Skip to content
KB

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.

  • CommunityToolkit.Mvvm (Microsoft, CommunityToolkit.Mvvm on 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 the ICommand wrapper, including the async Task-returning case as AsyncRelayCommand. 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 ContentControl a 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 and Microsoft.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-structure builds 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 implements IScreen holds a RoutingState navigation stack, a RoutedViewHost in the view shows whatever IRoutableViewModel is 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 onto Microsoft.Extensions.DependencyInjection for 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 ShellViewModel binds to a view named ShellView by 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.

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]

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.

What project structure fits a WPF app that calls a REST API with CommunityToolkit.Mvvm? (dotnet, howto)What project structure fits aWPF app that calls a REST API…
Open in the graph

Source: https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/overview

History: 4 changes
  1. Short answer changed; body changed
  2. Body changed
  3. Made public
  4. Created