Who should read this
Summary: WPF shipped in 2006 but remains central to enterprise desktop applications in 2026. The problem is not “WPF itself” but “WPF code written in a 2010-era style.” Combining .NET 9 + Generic Host + CommunityToolkit.Mvvm brings ASP.NET Core-grade DI, configuration, and logging to the desktop while cutting MVVM boilerplate by 90%. This article covers architecture setup for new WPF projects and incremental modernization strategies for existing ones.
This article is for C# developers maintaining or building new WPF applications. It also serves as a reference for teams considering migration from .NET Framework 4.x to .NET 9.
WPF in 2026: not dead, not replaced
“Is WPF legacy?” — No. Microsoft actively supports WPF in .NET 9, and WPF’s share of the Windows desktop app market remains dominant. Financial trading terminals, medical imaging viewers, industrial control systems, and enterprise admin tools often have no realistic alternative to WPF.
| WPF (.NET 9) | WinUI 3 | MAUI | Avalonia | |
|---|---|---|---|---|
| Maturity | 20 years, highly stable | Growing, partly incomplete | Growing, mobile-centric | Mature, cross-platform |
| Windows support | Win7 through Win11 | Win10 1809+ only | Win10+ | Win/Mac/Linux |
| Ecosystem | Largest (Telerik, DevExpress, etc.) | Growing | Limited | Growing |
| Existing asset compat | 100% XAML compatible | Similar XAML, not compatible | Similar XAML, not compatible | Similar XAML, not compatible |
| DirectX integration | DirectX 9/11 | DirectX 12 (WinAppSDK) | Platform-dependent | Skia-based |
| Best-fit scenario | Enterprise LOB, legacy modernization | Windows-only new apps | Cross-platform mobile + desktop | Cross-platform desktop |
Modern WPF architecture — the four core layers
Overall structure
MyApp.sln
├── MyApp/ # WPF project (entry point)
│ ├── App.xaml / App.xaml.cs # Generic Host integration
│ ├── Views/ # XAML View files
│ ├── appsettings.json # Configuration file
│ └── Hosting/ # Host wiring code
├── MyApp.Core/ # Pure C# library (no UI dependency)
│ ├── ViewModels/ # CommunityToolkit.Mvvm ViewModels
│ ├── Models/ # Domain models
│ ├── Services/ # Business logic
│ └── Interfaces/ # Service interfaces
└── MyApp.Tests/ # Unit tests
└── ViewModelTests/
Core principle: ViewModels and business logic live in a pure .NET project with no WPF references. This lets you run ViewModels in a console app or test project without WPF.
Layer 1: Generic Host integration — App.xaml.cs
Bridging WPF’s Application lifecycle with the .NET Generic Host lifecycle is the first step.
Benefits of this pattern:
- DI container — constructor injection for dependency management; no more direct instantiation with
new - Configuration management —
appsettings.json+ environment-specific overrides (appsettings.Development.json) - Logging — standard
ILogger<T>interface with Serilog/NLog plugins - HttpClient —
IHttpClientFactorypattern to prevent socket exhaustion
Layer 2: CommunityToolkit.Mvvm — eliminating boilerplate
CommunityToolkit.Mvvm (formerly Microsoft.Toolkit.Mvvm) is a source-generator-based MVVM library. Add attributes and the code is generated at compile time.
Traditional approach (manual INotifyPropertyChanged):
// 30 lines -- this much boilerplate for a single field and a single command
public class MainViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
private ICommand _saveCommand;
public ICommand SaveCommand => _saveCommand ??=
new RelayCommand(Save, () => !string.IsNullOrEmpty(Name));
private void Save() { /* ... */ }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
CommunityToolkit.Mvvm approach:
What the source generator creates automatically:
Nameproperty (get/set +PropertyChangedevent firing)SaveCommand(IRelayCommandimplementation)CanExecutewiring (NotifyCanExecuteChangedFor)
Layer 3: Service layer — interface-based design
Key patterns:
- Interface segregation — ViewModels depend only on
IDataService; the implementation is injected by DI - Async-first — all I/O operations use
async/awaitto prevent UI thread blocking - CancellationToken — allows canceling in-progress queries when the user navigates away
- DbContextFactory — creates a fresh
DbContexteach time to avoid concurrency issues
Layer 4: View — keep XAML thin
View principle: No business logic in code-behind. Only DataContext assignment and pure UI interactions (drag-and-drop, focus management — things that cannot be done in XAML) are allowed.
Navigation: screen transition patterns
WPF screen transitions generally follow one of two patterns.
| ContentControl swap | Window-based navigation | |
|---|---|---|
| Structure | Single Window + swap ContentControl.Content | Separate Window per screen |
| Best-fit scenario | Tab/sidebar SPA-style layout | Dialogs, independent windows |
| DI integration | INavigationService swaps ViewModels | Window created via DI |
| State management | Easy to control ViewModel lifetime | State lost when Window closes |
| Recommendation | Suits most LOB apps | Suits settings windows, modal dialogs |
Testing: ViewModels tested without WPF
The greatest benefit of CommunityToolkit.Mvvm + interface-based design is that ViewModel unit tests require no WPF reference.
Modernizing an existing WPF project
.NET Framework to .NET 9 migration sequence
Phase 1: Build system conversion (low risk)
- Convert
.csprojto SDK-style (dotnet try-converttool) - NuGet
packages.configtoPackageReference - Still targeting .NET Framework at this point
Phase 2: .NET 9 target switch (medium risk)
- Change
<TargetFramework>:net48tonet9.0-windows - Replace incompatible NuGet packages
- WCF to gRPC/REST, Remoting to direct communication
Microsoft.Windows.Compatibilitypackage for Win32 API compatibility
Phase 3: Architecture modernization (low risk, incremental)
- Introduce Generic Host + DI (modify App.xaml.cs)
- Convert ViewModels to CommunityToolkit.Mvvm one at a time
- Extract service layer interfaces + register in DI
- Migrate existing singletons/static classes to DI-managed services
Phase 4: Add tests (incremental)
- Add unit tests starting with newly converted ViewModels
- For legacy code, add tests only when making changes (Boy Scout Rule)
WPF vs full replacement: decision criteria
- Win10+ only support is acceptable, and you need WinUI 3’s latest controls
- Cross-platform is mandatory (Mac/Linux — choose Avalonia)
- Mobile coverage is required (choose MAUI)
- Little to no existing WPF code, making this effectively greenfield development
In all other cases, existing WPF + .NET 9 + Generic Host + CommunityToolkit.Mvvm is the most realistic 2026 choice.
Anti-patterns to avoid
Conclusion: WPF is not legacy — “WPF code written with legacy patterns” is legacy
The core stack for modern WPF architecture in 2026:
- .NET 9 — latest runtime, performance improvements, C# 13 syntax
- Generic Host — DI + Configuration + Logging integration
- CommunityToolkit.Mvvm — source generators eliminate MVVM boilerplate
- EF Core + DbContextFactory — async data access
- xUnit + NSubstitute — ViewModel unit testing
With this stack, you achieve the same level of structure, testability, and maintainability as an ASP.NET Core project — in a desktop app. You do not need to replace WPF. You need to change the structure of the code written on top of WPF.
Further reading
- Programming Language Comparison 2026: A Selection Guide by Strengths, Performance, and Use Case — Where C# stands relative to other languages
- The Evolution of Software Architecture: From Mainframes to AI Agents — The big picture from desktop to web to cloud
- Modular Monolith vs Microservices — Architecture choices for desktop app backends