← Back to list

Modern WPF Architecture in .NET 8: Building Maintainable Desktop Apps Beyond MVVM

Why most WPF applications become hard to maintain — and how to fix it using clean architecture, dependency injection, and Generic Host

Siva Bankapalli · 2026-05-09 06:32 · 0 claps · 3.4 min read
#wpf #software-architecture #csharp #dotnet-core #mvvm
Open on Medium ↗
Wiki topics: 🏛️ · Architecture

Modern WPF Architecture in .NET 8: Building Maintainable Desktop Apps Beyond MVVM

Why most WPF applications become hard to maintain — and how to fix it using clean architecture, dependency injection, and Generic Host

Most WPF applications don’t fail on day one They fail slowly. What starts as a clean MVVM project gradually turns into:

  • logic leaking into code-behind
  • ViewModels growing beyond control
  • hidden dependencies everywhere
  • code that works — but is hard to change

If you’ve worked on a real WPF system, you’ve probably seen this. The problem is not WPF. The problem is how we structure it.

The uncomfortable truth about MVVM MVVM is often treated as the solution. In reality, it is only a pattern — not a complete architecture. Many applications that “follow MVVM” still look like this:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
}

At first, this feels simple and clean. Over time, the cracks appear:

  • ViewModels start creating services directly
  • Dependencies become implicit and unmanaged
  • Testing becomes difficult
  • Changes introduce unintended side effects

MVVM is a pattern. Architecture is a discipline.

What actually happens in real projects

In theory, we say:

  • we separate concerns
  • we keep UI and business logic apart

In practice:

  • ViewModels handle API calls, validation, navigation, and UI state
  • services are instantiated directly inside ViewModels
  • code-behind is used for “quick fixes”
  • no one knows where a change should go

This is how complexity builds up — not suddenly, but steadily.

A better way to think about WPF architecture

Instead of focusing only on MVVM, think in terms of layers. **UI Layer

  • **Views (XAML)
  • minimal code-behind
  • responsible only for presentation **Application Layer
  • **ViewModels
  • commands
  • orchestration of UI behavior **Domain / Services Layer
  • **business logic
  • API calls
  • data processing

This separation creates clarity. Each part of the system has a clear responsibility.

The principle that changes everything

ViewModels should not create their dependencies. Dependencies should be provided to them. This one decision improves:

  • testability
  • maintainability
  • scalability

From manual wiring to structured composition

Before

DataContext = new MainViewModel();

After

public MainWindow(MainViewModel viewModel)
{
    InitializeComponent();
    DataContext = viewModel;
}
public MainWindow(MainViewModel viewModel)
{
    InitializeComponent();
    DataContext = viewModel;
}

The difference may look small, but it is not. In the second version:

  • dependencies are explicit
  • creation is controlled
  • the application has a clear structure

Bringing modern .NET practices into WPF

In modern applications, we expect:

  • dependency injection
  • logging
  • configuration
  • structured startup These are standard in web applications, but often missing in WPF. The missing piece is the Generic Host.

Using Generic Host in WPF

The Generic Host allows WPF applications to follow the same structured startup model as modern .NET applications.

public partial class App : Application
{
    public static IHost AppHost { get; private set; }

    public App()
    {
        AppHost = Host.CreateDefaultBuilder()
            .ConfigureServices((context, services) =>
            {
                services.AddSingleton<MainWindow>();
                services.AddSingleton<MainViewModel>();
                services.AddSingleton<IDataService, DataService>();
            })
            .Build();
    }

    protected override async void OnStartup(StartupEventArgs e)
    {
        await AppHost.StartAsync();

        var mainWindow = AppHost.Services.GetRequiredService<MainWindow>();
        mainWindow.Show();

        base.OnStartup(e);
    }
}

This introduces a composition root — a single place where dependencies are defined and managed.

A ViewModel with clear responsibility

public class MainViewModel
{
    private readonly IDataService _dataService;

    public MainViewModel(IDataService dataService)
    {
        _dataService = dataService;
    }

    public async Task LoadData()
    {
        var data = await _dataService.GetUserAsync();
    }
}

This ViewModel:

  • does not create dependencies
  • focuses on coordination
  • remains easy to test

Why this approach works in real systems

Testability Dependencies can be mocked and tested in isolation. Maintainability Clear separation reduces complexity. Scalability The system grows without breaking structure.

Common mistakes that cause long-term problems

Creating services inside ViewModels:

var service = new DataService();

Placing business logic in code-behind:

private void Button_Click(object sender, RoutedEventArgs e)
{
    // business logic
}

Overloading ViewModels with multiple responsibilities. These patterns work in small applications but break down as complexity increases.

A practical project structure

MyApp
│
├── src
│   ├── MyApp.UI
│   │   ├── Views
│   │   │   └── MainWindow.xaml
│   │   ├── App.xaml
│   │   ├── App.xaml.cs
│   │   └── Resources
│   │
│   ├── MyApp.Application
│   │   ├── ViewModels
│   │   │   └── MainViewModel.cs
│   │   ├── Commands
│   │   │   └── AsyncRelayCommand.cs
│   │   ├── Interfaces
│   │   │   ├── IDataService.cs
│   │   │   └── INavigationService.cs
│   │   └── Services (optional)
│   │
│   ├── MyApp.Domain
│   │   └── Models
│   │       └── User.cs
│   │
│   ├── MyApp.Infrastructure
│   │   ├── Services
│   │   │   ├── DataService.cs
│   │   │   └── NavigationService.cs
│   │   └── Configurations (optional)

GitHub Reference

A full working implementation is available here: 👉 https://github.com/sivabankapalli/wpf-modern-architecture-template This repository includes:

  • Clean architecture setup
  • Dependency injection
  • Generic Host integration
  • Logging and testing

What this means for your WPF application

If your WPF application is growing, structure is no longer optional. Without structure:

  • changes become risky
  • testing becomes difficult
  • onboarding slows down

With structure:

  • responsibilities are clear
  • dependencies are controlled
  • the system evolves predictably

WPF does not need a new framework. It needs better structure.

What’s next

In the next article: Designing a production-ready WPF project structure that scales across teams


메타데이터
post_id
bd336899caa6
slug
modern-wpf-architecture-in-net-8-building-maintainable-desktop-apps-beyond-mvvm-bd336899caa6
url
https://medium.com/@siva_bankapalli/modern-wpf-architecture-in-net-8-building-maintainable-desktop-apps-beyond-mvvm-bd336899caa6
canonical_url
https://medium.com/@siva_bankapalli/modern-wpf-architecture-in-net-8-building-maintainable-desktop-apps-beyond-mvvm-bd336899caa6
author_url
https://medium.com/@siva_bankapalli
status
ok
fetched_at
2026-06-14 11:28:49