ViewModel-first navigation with Prism

When using Prism, it's common to end up with code like this:

private void ShowHome() 
{
    var view = CreateView();
    var viewModel = CreateViewModel();
    view.DataContext = viewModel;

    var region = regionManager.Regions["SomeRegion"];

    region.Add(view, null, true);
    retion.Activate(view);
}

Using MVVM, we can make the assumption that every view has a view model following a naming convention - HomeView will always have a HomeViewModel.

Here's a shorter way we could write it:

private void ShowHome() 
{
    regionManager.AddViewModel<HomeViewModel>("SomeRegion");
}

The following extension method shows how AddViewModel might be implemented:

public static void AddViewModel<TViewModel>(this IRegionManager regionManager, string regionName)
{
    // Figure out the view based on the ViewModel class 
    var viewTypeName = typeof (TViewModel).FullName.Replace("Model", "View");
    var viewType = typeof (TViewModel).Assembly.GetType(viewTypeName);

    // Build the view and model, and bind them
    var view = (FrameworkElement)ServiceLocator.Current.GetInstance(viewType);
    var model = ServiceLocator.Current.GetInstance<TViewModel>();
    view.DataContext = model;

    // Render
    regionManager.Regions[regionName].Add(view, null, true);
    regionManager.Regions[regionName].Activate(view);
}

The direct reference to ServiceLocator from within the extension method is a code smell, and makes testing a little messy. There are some tricks we could use like wrapping the RegionManager in some kind of object which has a ViewLocator or ViewModelLocator, but you get the picture.

Parameters

To pass parameters to the view model, we could write this:

private void EditCustomer() 
{
    regionManager.AddViewModel<EditCustomerViewModel>("SomeRegion", customerId => 31);
}

Our view model constructor could look like this:

public class EditCustomerViewModel
{
    public EditCustomerViewModel(int customerId, ILogger logger, IFoo foo, IBar bar) 
    {
        ...
    }
}

The other constructor parameters will be resolved by the IOC container, but the customerId is a parameter we'd like to pass manually. At this point, using the service locator isn't enough - it doesn't support parameter passing.

A picture of me

Welcome, my name is Paul Stovell. I live in Brisbane and work on Octopus Deploy, an automated deployment tool for .NET applications.

Prior to founding Octopus Deploy, I worked for an investment bank in London building WPF applications, and before that I worked for Readify, an Australian .NET consulting firm. I also worked on a number of open source projects and was an active user group presenter. I was a Microsoft MVP for WPF from 2006 to 2013.