January 28, 2012

WP7 Series Part 2: Ensuring Blendability with Dependency Injection

Starting with MVVM


In WP7 Series Part 1 I explained that I wanted to build a Windows Phone application using some basic MVVM principles to maintain 'Blendability'. After tinkering with some options, I've decided to use the MVVM Light Toolkit as the basic framework. It provides some good tools for the toolbox but doesn't otherwise drastically change how I code.

After installing the toolkit and creating a new MVVMLight phone application, open the App.xaml file and you'll find an entry in the Application.Resources section that looks like this:

<Application.Resources>
    <!--Global View Model Locator-->
    <ViewModel:ViewModelLocator x:Key="Locator" 
                                d:IsDataSource="True" />
</Application.Resources>

This 'ViewModelLocator' is the heart of the MVVM Light framework and is meant to be used by every page to find its data context. It does so by assigning a property of the Locator resource to the page's context, like so (at the top of MainPage.xaml):

DataContext="{Binding Main, Source={StaticResource Locator}}"

This line basically says "bind my DataContext to the 'Main' property of the 'Locator' resource". Everything so far is great, and pretty straightforward. A quick look inside the ViewModelLocator.cs file shows that the 'Main' property is a static instance of the MainViewModel.cs class. I'm not going to go into the details of how the ViewModelLocator instanciates the ViewModels, though, because I don't particularly like how it works. I'll show my variation a bit farther down.

Meanwhile, let's glance inside the MainViewModel.cs class. One of the first things I notice is the way DesignTime vs RunTime data is decided.

public MainViewModel()
{
    if (IsInDesignMode)
    {
        // Code runs in Blend --> create design time data.
    }
    else
    {
        // Code runs "for real"
    }
}

With this method, every ViewModel is going to have the same (or very similar) logic duplicated in its constructor. I'd rather have that logic centralized some place else, and have the ViewModel ignorant of its source of data (DesignTime or RunTime). It just uses whatever data it gets.

Enter Dependency Injection


Back to the ViewModelLocator. Since the ViewModelLocator is where the ViewModel classes get instanciated, it seems an appropriate place to decide where to get the data for the ViewModels. I understand that the MVVM Light Toolkit may eventually include a small Inversion of Control container (called SimpleIoc), but I'm comfortable with Ninject, so that's what I ended up using. It takes just a few seconds to install Ninject using NuGet.

In 'Blendable' apps, the concepts of design-time data and run-time data are equally important, so I decided to treat design-time data as an integral part of my data access layer. I like using the Repository pattern, even though its popularity may be waning in some circles, so it makes sense that I should have both design-time and run-time data repositories available.

After creating the repositories (as shown on the right, each implementing the IExampleRepository interface), I created Ninject modules for each of them. You'll notice I created a ViewModelModule; I use that in the ViewModelLocator also.

Now, returning to the ViewModelLocator, I can create a Ninject kernel, and add either the DesignTimeModule or the RunTimeModule depending on the same IsInDesignMode property that the MainViewModel was using. You can see that below in the BuildKernel() method.

My ViewModelLocator now looks like this:

public class ViewModelLocator
{
    private static StandardKernel _kernel;
 
    public ViewModelLocator()
    {
        BuildKernel();
    }
 
    public IMainViewModel Main
    {
        get { return _kernel.Get<IMainViewModel>(); }
    }
 
    private static void BuildKernel()
    {
        _kernel = new StandardKernel(new ViewModelModule());
 
        if (ViewModelBase.IsInDesignModeStatic)
        {
            _kernel.Load(new DesignTimeDataModule());
        }
        else
        {
            _kernel.Load(new RunTimeDataModule());
        }
    }
}

The final piece of the puzzle comes into place by changing the MainViewModel so that it's constructor asks for an IExampleRepository to be injected. The ViewModel itself doesn't care whether it's design-time data or run-time data.

The MainViewModel now looks like this:

public class MainViewModel : ViewModelBaseIMainViewModel
{
    private readonly IExampleRepository _repository;
 
    public MainViewModel(IExampleRepository repository)
    {
        _repository = repository;
    }
 
    public string ApplicationTitle
    {
        get { return "MVVM LIGHT"; }
    }
 
    public string PageName
    {
        get { return "My page:"; }
    }
 
    public string Welcome
    {
        get { return _repository.GetWelcomeMessage(); }
    }
}

The 'Welcome' message (displayed in the middle of the MainPage) is now retrieved through the injected repository, which may be either run-time or design-time data. The end result, in the emulator:


In Visual Studio, design view :



And, finally, in Expression Blend :


So there you have it: one way of injecting design-time and run-time data into a ViewModel using the ViewModelLocator and Dependency Injection, with just a few changes to the out-of-the-box MVVM Light template.

The code for this sample can be found at https://bitbucket.org/devadept/mvvmlightsample/

Read all posts in the WP7-Series