Should a WPF app use an MVVM framework, or is plain WPF and modern C# enough?
howto · dotnet · csharp · wpf · mvvm
Use one: CommunityToolkit.Mvvm for the boilerplate (property-changed notification, commands) via source generators, plus Prism if the app also needs region-based navigation or a module system. Prism's DI container is not the reason to add it: the .NET Generic Host gives any WPF app a DI container on its own, and Prism 9's own container is dual-licensed, free only below a revenue or a funding threshold. Modern C# removed the reason to write `INotifyPropertyChanged` by hand, not the value of not writing it.
Why “pure WPF” still means picking a library
Section titled “Why “pure WPF” still means picking a library”WPF’s own INotifyPropertyChanged and ICommand are enough to build MVVM by hand: raise
PropertyChanged in every setter, write an ICommand implementation, wire it up in the view model
constructor. Doing that across a real app is not hard, it is just the same boilerplate on every
property and every command, and it is exactly the code a source generator can write instead. That
is the gap the surviving frameworks fill in 2026 - none of them do something WPF cannot; they remove
repetition WPF makes you write.
The options and what each actually adds
Section titled “The options and what each actually adds”- CommunityToolkit.Mvvm (Microsoft,
CommunityToolkit.Mvvmon NuGet) - not a framework so much as a code generator: mark a field[ObservableProperty]and it generates the public property with change notification, mark a method[RelayCommand]and it generates theICommandwrapper, including theasync Task-returning case asAsyncRelayCommand. It has no opinion on navigation, DI or app structure, has no UI framework dependency (the same package works in WPF, WinUI, MAUI, Avalonia, Uno), and it is the official successor to the older, now-unmaintained MVVM Light. Pick this by default; the only real question is whether that is enough on its own. - Prism - adds what CommunityToolkit.Mvvm deliberately leaves out: region-based navigation (name a
ContentControla region, and a view model can send a view to it by name instead of the shell hard-referencing every page) and a module system for loading parts of the app independently. It also ships its own DI container (DryIoc by default, with adapters for Unity andMicrosoft.Extensions.DependencyInjection), but that is not what earns it a place in a WPF app: the .NET Generic Host gives any WPF app a container on its own (dotnet/wpf-rest-api-project-structurebuilds one with no Prism in sight), so a DI need alone is not a reason to add Prism, only navigation between views not referenced at compile time or loading modules the shell doesn’t know about in advance. Prism 9 is also dual-licensed: the Prism Community License is free for an organization that clears either of two bars - under $1M USD annual gross revenue, or never having taken more than $3M USD in outside capital (private equity, venture capital); a commercial license, sold per developer, is required only for an organization past both - worth weighing at the point the choice is made, not after the app is built on it (Prism 8.1 and earlier stay MIT-licensed). It is commonly paired with CommunityToolkit.Mvvm for the view-model boilerplate, since the two are not mutually exclusive. - ReactiveUI - built on Rx.NET (
System.Reactive): view models expose observables and compose them (WhenAnyValue,ObservableAsPropertyHelper) instead of raising events by hand. It pays off when the app’s state is genuinely reactive - values derived from combinations of other values, async validation, search-as-you-type - and is overhead otherwise: Rx has its own learning curve and debugging story that plain property setters don’t. It does have a navigation and a DI story, just lighter-weight ones than Prism’s: a view model that implementsIScreenholds aRoutingStatenavigation stack, aRoutedViewHostin the view shows whateverIRoutableViewModelis current on it, and view/view-model resolution and other cross-cutting services go through Splat, the minimalist service locator ReactiveUI ships with (Locator.CurrentMutable.RegisterLazySingleton), which also has an adapter ontoMicrosoft.Extensions.DependencyInjectionfor a project that already has a container. Reach for it over Prism’s regions when the navigation stack is genuinely a stack (drill in, go back) rather than several independent regions updated at once. - Caliburn.Micro - older convention-based framework (a view model named
ShellViewModelbinds to a view namedShellViewby naming convention alone, action methods bind to buttons by name). Still used in existing apps built on it; not a first choice for a new one in 2026, since the convention magic it trades boilerplate for is now largely redundant with what source generators give explicitly. - MVVM Light - superseded. Its own author, Laurent Bugnion, now points users at CommunityToolkit.Mvvm, which the .NET Community Toolkit team built as its direct successor and migration path. Do not start a new project on it.
The trap: treating “framework vs. no framework” as the real choice
Section titled “The trap: treating “framework vs. no framework” as the real choice”The question as usually asked - framework or pure WPF - hides the actual decision, which is how much of the app’s shape a library should dictate. CommunityToolkit.Mvvm answers “no” to that: it saves typing without imposing navigation or structure, so adding it has close to no downside even for a small app. Prism and ReactiveUI both answer “yes” to a specific piece of structure (navigation/modules, or reactive composition) and are worth their weight only when the app actually has that problem; added to a simple CRUD-shaped app, they add concepts to learn without removing a pain point that existed. So the practical default is: always take CommunityToolkit.Mvvm, and layer Prism or ReactiveUI on top only once a concrete need (multi-module navigation, or genuinely reactive state) shows up.
How to verify the choice fits
Section titled “How to verify the choice fits”Add the package and convert one view model: if [ObservableProperty] and [RelayCommand] remove
most of that view model’s boilerplate with the app’s existing bindings unchanged, CommunityToolkit.Mvvm
alone is doing its job. Reach for Prism specifically when the app needs to navigate between views it
doesn’t hard-reference at compile time, or load a module without the shell knowing about it in
advance; reach for ReactiveUI when a view model’s properties are naturally derived from other
properties or asynchronous streams rather than set directly. The same decision, folding in the two
exceptions below:
flowchart TD
A{"Existing app on Caliburn.Micro<br/>or MVVM Light?"} -- yes --> B[Keep it, migrate opportunistically]
A -- no --> C{"One-window utility,<br/>two or three properties?"}
C -- yes --> D[Hand-written INotifyPropertyChanged]
C -- no --> E[CommunityToolkit.Mvvm]
E --> F{"Navigates views not referenced<br/>at compile time, or loads modules?"}
F -- yes --> G[+ Prism]
F -- no --> H{"Values derived from other<br/>values, or async streams?"}
G --> H
H -- yes --> I[+ ReactiveUI]When this does not apply
Section titled “When this does not apply”A one-window utility with two or three bound properties gets little from any of these; hand-written
INotifyPropertyChanged for that case is not a problem worth solving. And a codebase already built
on Caliburn.Micro or MVVM Light does not need a rewrite onto CommunityToolkit.Mvvm just because it is
now the default recommendation - migrate an existing app opportunistically, if at all, not preemptively.
Source: https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/overview
History: 4 changes
- Short answer changed; body changed
- Body changed
- Made public
- Created