/ MEF

MVVM with MEF: ViewModelFactory

If you use MEF to construct your view models, you have no way to pass an arbitrary object to the constructor of the view model. But you might need this, for example, when you have a list of data items and for each item you have a view model, then you usually need to pass the data item to the constructor of the view model. In such cases you are stuck with something like this:

OrderViewModel orderViewModel = new OrderViewModel(order);
_container.ComposeParts(orderViewModel);

There are three problems with this code:

  • It requires a reference to the MEF’s CompositionContainer (_container) which you have to carry with you all the time;

  • It consists of two lines of code. If you need to do it many times in different places there will be code duplication, which is not very good;

  • And finally you tie your code to MEF and if you ever decide to use different IoC Container, you’ll have to change all such places

  • (Bonus) You will have memory leaks in your application because MEF container stores references to the composed parts and the only way to release those references is to dispose the container (which is not an option if you have one shared container in your application).

The solution to all those problems would be some kind of helper that would encapsulate this logic in one place and eliminate the need to have a reference to CompositionContainer. So here it is, ViewModelFactory:

[Export(typeof(IViewModelFactory))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class ViewModelFactory : IViewModelFactory
{
    [Import]
    public Lazy<CompositionContainer> Container { get; set; }
 
    public T Create<T>(params object[] args) where T : class
    {
        // Creating a temporary child container that we will Dispose after the view model
        // is initialized in order to avoid memory leaks. The memory leak occurs because MEF
        // holds the reference to the exported parts forever. 
        // See http://www.friendlyninja.com/category/mef/ and 
        // http://mef.codeplex.com/wikipage?title=Parts%20Lifetime for details.
        var tempContainer = CreateTemporaryDisposableContainer(Container.Value);
 
        T result;
 
        try
        {
            bool populateDependencies = false;
 
            if (args == null || args.Length == 0)
            {
                // There are no parameters for contructor, so
                // try to create and instance by asking the container.
                result = tempContainer.GetExportedValueOrDefault<T>();
 
                if (result == null)
                {
                    // The view model is not exported. Just create an instance using 
                    // reflection and then populate all the dependencied using the 
                    // container.
                    result = Activator.CreateInstance<T>();
                    populateDependencies = true;
                }
            }
            else
            {
                // There are constructor parameters. Create an instance using those 
                // parameters
                // and then populate all the dependencied using the container.
                result = (T)Activator.CreateInstance(typeof(T), args);
                populateDependencies = true;
            }
 
            // Populate dependencies if needed
            if (populateDependencies)
            {
                tempContainer.ComposeParts(result);
            }
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(
                string.Format(
                    "Unable to create and configure an instance of view model of type {0}." +
                    " An error occured. See inner exception for details.",
                    typeof(T)), ex);
        }
        finally
        {
            // Disposing the temporary container to remove the references to the 
            // created view model from the container.
            tempContainer.Dispose();
        }
 
        return result;
    }
 
    private static CompositionContainer CreateTemporaryDisposableContainer(
        CompositionContainer originalContainer)
    {
        // The scope of the child container contains only the NonShared exports.
        // This means that Shared exports and their lifteime will still be tied 
        // to the parent container.
        // This is important to ensure that Shared exports exist only once throughout 
        // the application and are not disposed early.
        var filteredCat = new FilteredCatalog(originalContainer.Catalog,
                                              def => IsPartNotShared(def));
 
        return new CompositionContainer(filteredCat, originalContainer);
    }
 
    private static bool IsPartNotShared(ComposablePartDefinition def)
    {
        const string partCreationPolicyKey = 
            CompositionConstants.PartCreationPolicyMetadataName;
 
        return def.Metadata.ContainsKey(partCreationPolicyKey)
               && ((CreationPolicy)def.Metadata[partCreationPolicyKey])
               == CreationPolicy.NonShared;
    }
}

The Create method accepts optional list of parameters that have to be passed to the constructor of the view model type. If any parameters were provided it tries to create an instance using reflection and then call ComposeParts on the created object. Otherwise it just calls MEF to provide the instance of the requested type. If MEF returns Null (in case if the requested view model misses the Export attribute) then we again use reflection to create the instance for us.

You probably noticed that we create a temporary container that we use for the view model creation. This is needed to avoid the aforementioned problems with memory leaks. That temporary container gets disposed after we are done with the creation of our view model, thus all the references from the container are removed. And don't forget to mark you view models with the [PartCreationPolicy(CreationPolicy.NotShared)] attribute, otherwise the same one instance of the view model will be returned every time you call the factory.

FilteredCatalog is used for creation of the temporary container. You can find a sample implementation of it here or here.

The factory itself can be injected as dependency to the parent view model (the base class for all view models could have a property for that) or can be requested using service locator. Since the factory implements a simple interface (IViewModelFactory) it is easy to mock it when you write unit tests for your view model.

Here is how the creation of OrderViewModel looks now:

OrderViewModel orderViewModel = viewModelFactory.Create<OrderViewModel>(order);
Pavlo Glazkov

Pavlo Glazkov

Programmer. Full stack, with a focus on UI. JavaScript/TypeScript, Angular, Node.js, .NET

Read More