Caliburn.Micro Conventions: How View and ViewModel Are Paired
The third post in a series looks at Caliburn.Micro's conventions: how the framework pairs a View with its ViewModel through naming rules and reflection. It walks through ViewLocator's two-step lookup, the five default regex rules in NameTransformer, and why AssemblySource determines whether a View can be found at all.
Key points
- ViewLocator exposes the `LocateForModel` and `LocateForModelType` Func hooks that return a View for a ViewModel instance or type.
- Matching happens in two stages: type inference, then reflection-based creation with `Activator.CreateInstance`.
- A ViewModel implementing `IViewAware` can cache its View, and ViewLocator returns the cached one before creating anything new.
- NameTransformer ships five default rules that also cover the ViewModels → Views folder separation.
- AssemblySource defines the search scope, so Views in separate assemblies such as plugins must be added through `SelectAssemblies`.
- You can skip convention matching entirely, build both objects yourself and wire them with `ViewModelBinder.Bind`.
For MVVM the recommended direction is ViewModel-First: model the ViewModel, then let a DataTemplate or runtime lookup supply the View. Caliburn.Micro handles that with ViewLocator, whose Func-based hooks `LocateForModel` and `LocateForModelType` return a View for a given instance or type. The lookup has two stages: derive the View type from the ViewModel type, then instantiate it with reflection through `Activator.CreateInstance`. If the ViewModel implements `IViewAware` and already cached a View, that cached instance is returned instead of a new one.
Type inference runs through NameTransformer's regex rules, where `ReplacePattern` and `ReplaceValue` rewrite the trailing ViewModel into View, so `ShellViewModel` maps to `ShellView`. The five built-in rules also cover the common ViewModels → Views folder split. Because the resolved value starts as a string, a second stage resolves it against the assemblies in AssemblySource; since the AppDomain is not scanned, Views living in separate assemblies such as plugins must be registered via an overridden `SelectAssemblies` or AssemblySource's Add/AddRange.
Real-world use
- A WPF desktop app with separate ViewModels and Views folders where the team wants runtime View resolution instead of hand-written new calls.
- An application that loads plugins via DirectoryCatalog and needs Views shipped inside those plugin assemblies to resolve automatically.
- Developers migrating away from Caliburn.Micro who want to understand how its naming conventions actually resolve Views.
Why it matters
Caliburn.Micro's automatic pairing only works when naming and assembly scope line up; a mismatched name or an unregistered AssemblySource shows up as a View that cannot be resolved at runtime.