Wednesday, February 3, 2010

Silverlight : MEF and MVVM

With the upcoming release of Silverlight 4, I had a look at the Managed Extensibility Framework, since it is shipped together with Silverlight 4 once it is released.

What is MEF ?

According to the codeplex project :

MEF provides a standard way for the host application to expose itself and consume external extensions. Extensions, by their nature, can be reused amongst different applications. However, an extension could still be implemented in a way that is application-specific. Extensions themselves can depend on one another and MEF will make sure they are wired together in the correct order (another thing you won't have to worry about). 
source : http://mef.codeplex.com/

Basically you can export members of a class, these will be viewed by MEF as parts, which in turn can be imported by other classes.

When you want to import those parts to you Silverlight view you have to use, PartInitializer which will satisfy all imports on the class with the exported types.

MEF and MVVM

This functionality gave me the idea to import a ViewModel to a View to there is no link between the both in either direction. Allowing to easily change ViewModels and Views and reuse them.

Defining the contract

To be able to export and import our ViewModel parts, we have to define a contract for them.

We will simply add the following empty interface that we must implement on all our ViewModels :

public interface IViewModelBase { }

public class ViewModel : IViewModelBase
{
public string Value
{
   get
   {
     return "Some Display Value";
   }
}
}

Exporting our ViewModel

The next step would be to export our ViewModel and expose it so it can be imported by our view.

To do this we could provide a custom MetaModel class to make sure we can differentiate our ViewModels when assigning them to our View, however since we are only interested in a single identifier, we can create a custom export attribute to automatically do this.

To do this we first create a contract that will be implemented as the MetaData.

public interface IViewModelMetaData
{
string Identifier { get; }
}

Next we will create our custom export attribute, we should make sure we inherit the original ExportAttribute and mark the class as metadata so the metadata contract can be interpreted when we are importing.

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ViewModelExportAttribute : ExportAttribute, IViewModelMetaData
{
public ViewModelExportAttribute(): base(typeof(IViewModelBase))
{}

public string Identifier { get; set; }
}

We can apply this to our ViewModel by adding the following attribute above the class definition.

[ViewModelExport(Identifier = "MyViewModelIdentifier")]

Importing the ViewModel

At this point we want to be able to link the exported ViewModel to our View, to do this we can create a abstract class we can use as a base for all our views, implementing some basic functionality to enable the import.

public abstract class ViewBase : UserControl

   protected ViewBase() 
  
       PartInitializer.SatisfyImports(this); 
   }  

   public abstract string ViewModelIdentifier { get; }  

   [ImportMany
   public Lazy<IViewModelBase,IViewModelMetaData>[] ViewDataContext 
  
       set 
      
           DataContext = value
           .Where(viewModel => viewModel.Metadata.Identifier == ViewModelIdentifier)
           .First().Value; 
       
    }
}

So what exactly did we do here ?   To start ,we have created a ViewDataContext property of type Lazy<IViewModelBase,IViewModelData> and marked it with the ImportMany attribute.

Once the StatisfyImports method is called, MEF will initialize all properties marked with the Import attribute, in our case it will initialize the ‘ViewDataContext’ property.  Since MEF will import all Parts of the given IViewModelBase contract, we need a way to identify which ViewModel we want to use on our View, we can do this by exposing an abstract property ‘ViewModelIdentifier’ that will have to implemented by all views. 

Doing this we can check the exported metadata for the Identifier and compare it to the ViewModelIdentifier defined in the view, when a match has been found, the ViewModel is assigned to the DataContext of the view and we can do all the usual MVVM magic.

4 comments:

  1. Hi,

    Great stuff :-) Why do you go with an [ImportMany] though and then restrict it down to a single matching export? That feels kind of equivalent to [Import]?

    i.e. could you use [Import("MyViewModelIdentifier", typeof(IViewModelBase))] and how would that compare with what you've got here?

    Mike.


    Mike.

    ReplyDelete
  2. Hi Mike,

    Yes that would work as well, to be honest, I hadn't considered that overload.

    This would indeed allow to change the ImportMany into a Import statement, and assigning the contractName in the ViewModelExportAttribute constructor. Getting rid of the list.

    Thanks for the heads up.

    ReplyDelete
  3. To continue on my previous reply, if you would like to check for your ViewModel using several metadata fields however, you would still be forced to use the ImportMany because you can't define these multiple fields directly into the Import attribute.

    ReplyDelete
  4. Hello maarten,
    This is ashok working on silverlight4. can u please tell me how can i implement mvvm and mef structure to my silverlight application. i have to create each of viewmodel , view ane model separately for my application.and make sure it willwork fine.
    thanx in advance.

    ReplyDelete