Tuesday, 15 November 2011

A reference architecture for large WPF projects

Introduction

Choosing an adequate architecture is crucial for the success of a software project. You can have the best concepts, if your architecture does not perform, the user will have bad experiences while waiting for the application to load. Also aspects like it's robustness, maintainability or testability are important to address.
WPF provides a powerful databinding framework. If we could take advantage of this by using the MVVM pattern and decouple our views by dependency injection, we can build a powerful scaleable architecture.
These are the key components or patterns we want to use:
  • WPF DataBinding
  • Model-View-ViewModel pattern
  • Dependency Container (e.g. Unity)
  • Actions from the System.Windows.Interactivity library

How it works

The basic idea is to have a dependency container that builds up a view. The view has a viewmodel injected, that is bound to the DataContext. The viewmodel concentrates and provides data and commands for the view that it gets from services, that are injected by the constructor. The services live as singletons within the container.
Using this architecture allows you to build up loosely coupled views that interact magically together over common data coming from services in the background. It's very simple to rearrange or replace views, since they have to dependencies among each other.
Advantages of this architecture:
  • UI elements are easily replaced because of flexible databinding
  • The views are loosely coupled and quickly composed together
  • The viewmodel can be tested with conventional unit testing
  • Each service has a single purpose. They are easy to develop and make the architecture scalable.

Initializing the container and build up the main window

 
public class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        IUnityContainer container = new UnityContainer();
        container.RegisterType<ICustomerService, CustomerService>();
        container.RegisterType<IShoppingCartService, ShoppingCartService>();
 
        MainWindow mainWindow = container.Resolve<MainWindow>();
        mainWindow.Show();
    }
}
 
 

Injecting the viewmodel to the view

By adding a [Dependency] attribute to the property, the dependency container resolves and injects the specified type after creating the view. The injected viewmodel is directly set to the data context of the view. The view itself contains no other logic.
 
public class MainWindow : Window
{
    [Dependency]
    public MainWindowViewModel ViewModel
    {
        set { DataContext = value; }
    }
 
    public MainWindow()
    {
        InitializeComponent();
    }
}
 
 

Implementing the viewmodel

 
public class MainWindowViewModel
{
    private ICustomerService _customerService;
 
    public MainWindowViewModel(ICustomerService customerService)
    {
        _customerService = customerService;
        Customers = new ListCollectionView(customerService.Customers);
    }
 
    public ICollectionView Customers { get; private set; }
}
 
 

Binding the data to the view

 
<Window x:Class="WpfTutorial.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ListBox ItemsSource="{Binding Customers}" />
</Window>
 

No comments:

Post a Comment