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.