Wednesday, March 17, 2010

ViewModel binding with the Managed Extensibility Framework

This is just a short post to share an "ah-hah" moment I experienced building out a large Managed Extensibility Framework (MEF) Silverlight application. Traditionally, I've wired in the view models using the code behind. I'm not one of those who believes code behind is evil no matter what, so I haven't taken issue with something like this:

[Import] 
public MainViewModel ViewModel 
{
   get { return LayoutRoot.DataContext as MainViewModel; }
   set { LayoutRoot.DataContext = value; }
}

This simply imports the view model that I need and then passes it along to the data context. The issue with XAML is that it doesn't allow me to pass in constructor information and always creates a "new" instance, so if the view model is part of a larger dependency chain, I'd get a new instance in every page that needed a copy.

This is an example of when having design patterns as a part of your vocabulary can improve your ability to solve problems. Obviously, it wasn't part of my vocabulary or I wouldn't have missed it! There is a nice pattern that neatly addresses the concern of having a view model wired in by MEF that allows for attaching it in XAML without having to use code behind. Again, I'm not opposed to code behind, but any time I can abstract the behavior further and do it in a natural way without jumping through hoops just to make it work is something I'll consider.

Enter the Service Locator design pattern. The challenge is that you have components that are based on service contracts and the concrete implementation not known at design time. In our case, the "service" is the view model, and it's not concrete yet because we haven't composed the parts.

Let's create our service locator for view models. In this example, I only have one, but in the application I am working on there are several, and they all get exposed via the service locator:

public class ViewModelLocator 
{
   public ViewModelLocator()
   {
      CompositionInitializer.SatisfyImports(this);
   }
  
   [Import]
   public MainViewModel MainViewModelInstance { get; set; }
}

It's that simple. The class itself composes the view model for us, then provides a property we can use to access the view model. Because we are importing via MEF, we can control the lifetime for the view model and any other attributes we need to compose it properly. Now, we can wire in the view model with no code behind. The XAML looks like this:

<UserControl ... xmlns:vm="clr-namespace:ViewModel">
<UserControl.Resources>
   <vm:ViewModelLocator x:Key="VMLocator"/>
</UserControl.Resources>
<Grid
   x:Name="LayoutRoot"
   DataContext={Binding Source={StaticResource VMLocator},Path=MainViewModelInstance}">
   ...
</Grid>

There you have it ... a simple, clean way to bind the view model to the view without code behind and still allow MEF to compose all of the parts for you.

Jeremy Likness

22 comments:

  1. This compares well with the ViewModelLocator class in Laurent Bugnion's MVVM Lite framework. Your implementation using MEF requires less code, and you get loose coupling of ViewModel implementations.

    ReplyDelete
  2. Hi,
    I have few questions
    1. In the locator class, Are we going to have all the viewmodels exposed as Lazy
    2. Lets say, I have to change the views displayed in a contentcontrol, am I going to properties of all the views as properties
    like below in the ViewModel

    [Import]
    public Lazy View1{get;set;}
    [Import]
    public Lazy View2{get;set;}

    ReplyDelete
  3. 1. We could, but we don't have to. For example, I may have a "main view model" used throughout that is a shared part. No need for lazy there ... we're not concerned about garbage collection because it persists throughout the lifetime of the application and the import in the locator simply becomes a reference to the singleton. For short-lived views, absolutely. Make it lazy, and only have it come into scope if/when needed. With a little clever magic you could also use ExportFactory and generate what you need with a WeakReference so when the control is done, it can go out of scope.

    2. Not sure I catch this. Swapping the views in a content control can have many implications. You might use a viewmodel-first approach that uses a locator similar to what Caliburn does and fires the view in, or you might use a region management pattern, or any other means.

    ReplyDelete
  4. Thanks for the reply.
    2. depending on what button a user clicked the contentcontrol is going to change its content. so either
    1. create the instance of the view using exportfactory or
    2. have an import in the viewmodel(for each type of the view I want to show
    right?

    ReplyDelete
  5. What about if you simply had to pass some conext about the view (say through an IView) such as editing a Customer and the View had an ID property, and you wanted to pass that to VM, how do you see this working???

    ReplyDelete
  6. Two ways I have done that in the past, Sacha (thanks for asking ... great question ... interested to know more about how Cinch handles this as well).

    First, I've based the view model on, say, BaseViewModel, with an exposed View property. On the view, which can also be based (ViewBase : UserControl, IView), the ViewBase hooks Loaded and then sets ((BaseViewModel)DataContext).View = this;

    Second, I've done it with attached behaviors. Using a behavior ala Blend, for example, you can simply hook OnAttached and if both the VM and View have a well defined interface, connect the two there.

    Do you have an article you'd like linked that explains how Cinch does this?

    ReplyDelete
  7. Jeremy,

    Can you tell me what this message means ?


    1) No valid exports were found that match the constraint '((exportDefinition.ContractName == "mvvmLight.ViewModels.ViewModel.RestaurantSearchViewModel") AndAlso (exportDefinition.Metadata.ContainsKey("ExportTypeIdentity") AndAlso "mvvmLight.ViewModels.ViewModel.RestaurantSearchViewModel".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))', invalid exports may have been rejected.

    I am exporting like so:

    public interface IRestaurantSearchViewModel { };

    [Export(typeof(IRestaurantSearchViewModel))]
    public class RestaurantSearchViewModel : ViewModelBase , IRestaurantSearchViewModel
    {

    What am I doing wrong ?

    Thanks,
    Greg

    ReplyDelete
  8. How are you importing it? It looks like you are importing a "RestarauntSearchViewModel" instead of a "IRestaurantSearchViewModel" ...

    ReplyDelete
  9. public class ViewModelLocator
    {
    public ViewModelLocator()
    {
    CompositionInitializer.SatisfyImports(this);
    }

    [Import]
    public IRestaurantSearchViewModel RestaurantSearchModelInstance { get; set; }
    }

    }

    ReplyDelete
  10. Maybe I need to decorate the interface ?

    [InheritedExport]
    public interface IRestaurantSearchViewModel { };

    ReplyDelete
  11. No, not necessarily. You shouldn't have to. You are exporting explicitly. The question now becomes whether or not the Restaurant View Model has some dependency that isn't being satisfied, causing the export to be rejected.

    ReplyDelete
  12. That was it !

    I was implicitly exporting the model for this viewmodel

    [Export]
    public class EstablishmentsModel : ModelBase, IEstablishmentsModel
    {

    Change it to explicitly declare the type and it worked

    [Export(typeof(IEstablishmentsModel))]
    public class EstablishmentsModel : ModelBase, IEstablishmentsModel
    {


    Thanks !!!

    ReplyDelete
  13. >>With a little clever magic you could also use ExportFactory and generate what you need with a WeakReference so when the control is done, it can go out of scope.
    Do you have a blog entry how this little magic should look like? i would really want to see ExportFactory and WeakReference in action :)

    ReplyDelete
  14. I'll see what I can put together for a future post. Thanks!

    ReplyDelete
  15. Hi Jeremy,
    The solution above solves controlling the life time of the ViewModel, what about controlling importing a ViewModel with a specific constructor?

    My analysis is, with the use of MEF, no need for specific constructors as we can IMPORT anything we need without the constructor, but again would like to hear your analysis on this.

    Thanks for the helpful material on your blog.

    Regards
    Bilal

    ReplyDelete
  16. Yes, I've moved completely away from importing constructors and just expose the dependencies as properties. Functionally, this is easy and makes sense because MEF can import these, while in unit tests you can simply set them. From a purist's point of view it exposes more about the internals of the class than some may like, but I say .... SO? I'm abstracting via interfaces, that gives me all I need.

    ReplyDelete
  17. Thanks for the swift reply!

    "while in unit tests you can simply set them", you mean setting directly the properties or using MEF to inject them? If yes, any example on how to inject Mocks/Stubs to properties of ViewModels using MEF?

    I tried to use the VMLocator on your example "RegionsWithMef - Part 1", It worked only when I kept the "CompositionContainer.SatisfyImports(this)". I thought in MEF, the "SatisfyImports" is hierarchical, in a sense, in the App.xaml.cs, there was a call to "SatisfyImports", hence Shell class was created (XAML was parsed). While XAML was parsed, the ViewModelLocator class was created and hence, its Imports should have been imported also, but seems not!

    Regards
    Bilal

    ReplyDelete
  18. I really wouldn't want to have MEF wiring my unit tests. That's the point of having decoupled parts - when I'm testing "module A" then any dependencies are mocked, and I mock those directly, no need for MEF. It's only in production where it wires all together.

    As for the composition initializer, it is hierarchical - anything that is referenced. In this case, when the locator is in XAML, the App doesn't have a direct reference, so the locator must also satisfy imports. If we imported it programmatically and set it, it would be fine.

    ReplyDelete
  19. Hi Jeremy,

    when I played around with ViewModelLocaor I tried the same approach as you. While at runtime everything is fine, I wasn't able to get it running at design time. The problem is also described in the mef discussion board http://mef.codeplex.com/Thread/View.aspx?ThreadId=208306 When the designer tries to create an instance of ViewModelLocator as a recourse the Satisfy()method in the ctor fails.

    ReplyDelete
  20. Whoops, I designed a UserControl using this code (WPF with CompositionInitializer + .NET 4).

    In runtime, it works OK, but when I try to add the UserControl the the parent's XAML it just states that it cannot creat an instance of the it (my UserControl has its ViewModel attached to it using your example).

    It can send you the code, it's plain a simple (a few lines, only). Thanks!!

    ReplyDelete
  21. Hi Jeremy,

    I'm using Silverlight 4 / PRISM 4 / MEF and I've having the following issue.

    I've got the scenario you describe at the top of your blog - a view importing a viewmodel in the code behind. It works fine but I'm keen to reduce the code behind and see that the viewlocator approach is attractive.
    I've changed my code to implement a viewlocator and the resource xaml is:

    local:CATProductsViewModelLocator x:Key="viewModelLocator"

    and the data binding xaml is:

    UserControl.DataContext
    Binding Source="{StaticResource viewModelLocator}" Path="Products"
    UserControl.DataContext

    and the ViewModel Locator code is:

    public class CATProductsViewModelLocator : IPartImportsSatisfiedNotification
    {

    public CATProductsViewModelLocator()
    {

    try
    {
    // debug code
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog);

    /*
    * This GetExportedValueOrDefault works but the Composition.SatisfyImports(this) does not.
    */
    var export = container.GetExportedValueOrDefault();
    //debug code

    CompositionInitializer.SatisfyImports(this);

    }
    catch
    {
    Debug.WriteLine("CAT.ProductsModule.ViewModels.CATProductsViewModelLocator imports NOT satisfied");
    }



    }

    [Import]
    public CATProductsViewModel Products { get; set; }

    public void OnImportsSatisfied()
    {
    Debug.WriteLine("CAT.ProductsModule.ViewModels.CATProductsViewModelLocator imports satisfied");
    }

    What's weird is that these are the only code changes from the previous (importing the VM in the view's code behind) which works. I've tried using mefx and the visual mefx and cannot get either of them to work.

    Am I missing something here or have you got any suggestions ?

    Regards.

    ReplyDelete
  22. Jeremy just a quick note thanks here. Am converting an old code behind code base over to MEF and MVVM and this answered just the question I was after (ie No code wiring up) RMH

    ReplyDelete