If you’ve spent any significant time in the WPF trenches over the last decade, chances are you’ve crossed paths with Caliburn.Micro. For many of us, it was the framework that finally made MVVM “click.” It stripped away the boilerplate, replaced verbose configuration with smart conventions, and let us focus on building features.
Now that .NET MAUI is the standard for cross-platform .NET development, you might be staring at your existing WPF codebases and wondering: “Do I have to learn a whole new MVVM framework? Do I have to give up my conventions?”
The answer is a resounding no.
Caliburn.Micro is alive and well in the .NET MAUI era. While the underlying platform has changed, the “magic” feels remarkably familiar. In this post, we’ll walk through how to migrate the core structure of a Caliburn.Micro WPF app to .NET MAUI.
The Paradigm Shift: Goodbye Bootstrapper#
In the WPF world, everything started with the Bootstrapper. You’d subclass BootstrapperBase, override OnStartup, and wire up your IoC container there. It was the heart of your application.
In .NET MAUI, the application lifecycle is different. We don’t have a Bootstrapper class anymore. Instead, the responsibilities of the bootstrapper have moved directly into the App class, which now inherits from Caliburn.Micro.Maui.MauiApplication.
Let’s look at how to set this up from scratch.
Step 1: The Setup#
First, create a new .NET MAUI project. Once you’re in, you’ll need to install the package. Be careful here—you don’t just want the core library; you need the platform-specific integration.
| |
Note: Ensure you are targeting .NET 9 or a compatible version, as Caliburn.Micro has kept pace with the latest platform updates.
Step 2: The New “Bootstrapper”#
Open your App.xaml.cs. In a standard MAUI app, this inherits from Application. We’re going to change that to Caliburn.Micro.Maui.MauiApplication.
This is where we configure our container and set the root view.
| |
Key Differences to Note:#
Initialize()in Constructor: We callInitialize()directly in the constructor.- Async Root View: We use
DisplayRootViewForAsync<T>(). MAUI’s navigation primitives are asynchronous, and Caliburn respects that. Configure(): This looks almost identical to the WPF version. Your DI logic (whether you useSimpleContaineror Autofac) lives here.
Step 3: The Entry Point#
MAUI uses a builder pattern in MauiProgram.cs to construct the app. We need to make sure our App class is registered correctly.
| |
Step 4: The Magic (Conventions)#
This is why we’re here. Does x:Name still automagically bind to properties? Yes.
Let’s create a simple MainViewModel:
| |
And the corresponding MainView.xaml (in the Views folder):
| |
Just like in WPF, Caliburn.Micro locates MainView based on the name MainViewModel, binds the Label to the Message property, and wires the Button to the ClickMe method. No Command boilerplate required.
Pitfalls & Gotchas#
While the MVVM structure is preserved, the platform underneath has shifted.
1. Async Navigation#
In WPF, TryClose() was synchronous. In MAUI, navigation is inherently async. You’ll find yourself using await more often when coordinating screens.
2. XAML Dialect#
Caliburn handles the binding, but it doesn’t translate the controls.
- WPF
StackPanel-> MAUIVerticalStackLayout/HorizontalStackLayout - WPF
TextBox-> MAUIEntryorEditor - WPF
TextBlock-> MAUILabel
You will still need to rewrite your XAML, but your ViewModels can often be ported with minimal changes (mostly changing namespaces).
Conclusion#
Migrating from WPF to MAUI is a significant undertaking, but sticking with Caliburn.Micro reduces the cognitive load immensely. You keep your architectural patterns, your testing strategies, and your muscle memory for conventions.
If you have a mature WPF application built on Caliburn, you don’t need to throw it all away. The framework has evolved with the times, proving once again that good abstractions are timeless.
For a complete working example, check out the official Setup.Maui sample in the Caliburn.Micro repository.
Happy coding!
