Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Blendability Part III – View Model Locator Replacement using MEF

4.00/5 (3 votes)
19 Jan 2011CPOL4 min read 16.3K  
Blendability Part III – View Model Locator Replacement using MEF

Everybody loves MEF! Don't you? I think MEF is one of the best things that happened to the latest .NET Framework 4.

Just in case you don't know what I'm talking about, I urge you to start reading about MEF in my colleague, Bnaya Eshet's blog. He has great MEF tutorials for beginners.

So why am I writing about MEF in this WPF related post anyway? Well MEF is a great framework for extensibility and object composition, also it can be used as a DI container, declaratively and imperatively.

With my experience in architecting several WPF client applications, I find MEF fits like a glove, makes everyone's life easier.

Let's have a simple sample of how may I use MEF in my client application.

C#
[Export(typeof(ICommandBarViewModel))]
public class CommandBarViewModel : NotificationObject, ICommandBarViewModel
{
    [Import]
    private ILogger Logger
    {
        get;
        set;
    }
 
    [Import]
    private IConfigurationService ConfigurationService
    {
        get;
        set;
    }        
 
    public IEnumerable<ICommandBarAction> Actions
    {
        get
        {
            return new ICommandBarAction[]
            {
                new CommandBarAction
                {
                    Content = "Open",
                    Command = new DelegateCommand<object>(p => Open())
                },
                new CommandBarAction
                {
                    Content = "Save",
                    Command = new DelegateCommand<object>(p => Save())
                }
            };
        }
    }
}

The sample code above demonstrates a simple usage of MEF for composing the CommandBarViewModel with both Logger and Configuration Service dependencies.

As you can see, the CommandBarViewModel type is decorated with MEF's ExportAttribute attribute, implementing the ICommandBarViewModel. Composing an instance of type CommandBarViewModel using MEF's container, satisfies all of its imports which are ILogger and IConfigurationService.

Implementing the MVVM pattern, especially as view-first, requires injection of the view-model instance into the relevant view which is created directly from XAML. There is a popular pattern called View Model Locator (a service locator pattern) which can be useful in such scenario.

Personally, I find the View Model Locator very awkward, as you should always create an instance of it and place it in a resource-dictionary, then you should use Binding and StaticResource markups to resolve it. Since you should also provide a hint of what view-model to create, you should set the Binding.Path with the view-model type or a different hint, or have a binding converter. This ends with something like this:

XML
<Application.Resources>
    <t:ViewModelLocator x:Key="viewModelLocator"/>
    <t:IndexerConverter x:Key="viewModelIndexerConverter" />
</Application.Resources>
C#
DataContext="{Binding 
    Source={StaticResource viewModelLocator}, 
    Converter={StaticResource viewModelIndexerConverter}, 
    ConverterParameter=MainViewModel}"

Needless to say that the implementation of the view model locator itself should deal somehow with a ridiculous index provided as "MainViewModel" hint to the indexer binding converter.

Using MEF with a simple markup extension I wrote, your XAML ends like this:

XML
<UserControl x:Class="Blendability.Solution.Parts.CommandBarView" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:ts="http://blogs.microsoft.co.il/blogs/tomershamam" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
    mc:Ignorable="d"d:DesignHeight="76" d:DesignWidth="460" 
    DataContext="{ts:Import ts:ICommandBarViewModel}"> 

    <ToolBar ItemsSource="{Binding Actions}"> 
        <ToolBar.ItemTemplate> 
            <DataTemplate> 
                <Button Style="{DynamicResource 
			ResourceKey={x:Static ToolBar.ButtonStyleKey}}" 
                       Content="{Binding Content}" 
                       Command="{Binding Command}" /> 
            </DataTemplate> 
        </ToolBar.ItemTemplate> 
    </ToolBar> 

</UserControl> 

Much more elegant and less fragile solution I think, and I haven't talked about the Blendability part yet. Please be patient… this is the interesting part.

The ImportExtension markup-extension I used from XAML is a custom markup-extension I wrote which helps with the UI composition. Working with view-first, we can easily create and compose our view-model with MEF directly from XAML. In our case, we should only provide the view-model contract, which is the Import markup single argument of type Type, and in our case: ICommandBarViewModel.

Have to say that contracts in MEF can be also a simple string, but string is fragile and this is only one of the reasons I've chosen an interface instead of a string. Other reasons are unit-testing and Blendability.

The simple trick here is composing the given type inside the Import extension using MEF.

Composing our view-models with MEF, we are solving many problems we should deal with in the development phase:

  1. Create a view-model with all of its dependencies from both XAML and code.
  2. Create a dummy view-model at design-time but real one at runtime.
  3. Create a view-model stub/mock for unit-testing.
  4. Resolve dependencies such as services without being care of objects life-time and origin.

So now that we agree (or not) that MEF is really cool with our MVVM pattern, let's talk about Blendability of view-models created by MEF.

First let's mention the d:DataContext XAML attribute added to VS2010, also exist in Blend 3 and 4. I've talked about it in my previous post: Blendability Part I – Design time ViewModel.

The d:DataContext XAML attribute is really helpful, but is it really necessary having two definitions of data-context, one for runtime and one for design-time?

Personally, I prefer having only one definition of data-context which will serve both runtime and design-time. Here we go back to old school, working with the DataContext property only and here is how:

XML
<UserControl x:Class="Blendability.Solution.Parts.CommandBarView"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:ts="http://blogs.microsoft.co.il/blogs/tomershamam"
   DataContext="{ts:Import ts:ICommandBarViewModel, IsDesigntimeSupported=True}">
  
    ... 
</UserControl>

As you can see, I'm using the same Import markup extension for having two different implementation of ICommandBarViewModel. One implementation for runtime, and one for design-time. The runtime implementation will be composed and return by the Import markup extension at runtime only, and the design-time implementation will be composed and return by the Import markup extension at design-time only.

And you may ask:

  1. How does DataContext work at design-time?
  2. What if I want to use the magnificent Blend sample data?
  3. What is and where the design-time implementation of ICommandBarViewModel coming from?

And the answers to these questions are:

  1. The same way as if you would create your view-model directly from XAML and set the DataContext property with. It supported by both VS and Blend.
  2. You can always remove the IsDesigntimeSupported=True and continue working with d:DataContext or you could add another property to the Import markup extension which is the object to use at design time, and use the regular d:SampleData to fetch it.
  3. It's getting too late over here, so I'll provide the answer + code to this one in my next post.

Next, I'll reveal design-time support for MEF and provide both the Import markup extension code and the rest. Stay tuned!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)