Support for MEF

Jul 7, 2010 at 8:44 AM

Hi.

I am struggling to implement MEF as my IoC container in the current Micro framework.

public static class IoC
{
    /// 
    /// Gets an instace by type and key.
    /// 
    public static Func<Type, string, object> GetInstance;
   
    ///.....
}

That implementation does not allow for the 'generic' type specifier to reach MEF?

What am I missing here.

Otherwise really excellent framework I am going to enjoy working with your ideas. 

Coordinator
Jul 7, 2010 at 12:47 PM
My next post will show how to do this.
Coordinator
Jul 7, 2010 at 12:49 PM
Edited Jul 7, 2010 at 12:50 PM

Here's a sample MEF bootstrapper to help out until I get the post finished:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;

public class MefBootstrapper : Bootstrapper<IShell>
{
    private CompositionContainer container;

    protected override void Configure()
    {
        container = CompositionHost.Initialize(
            new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
                )
            );

        var batch = new CompositionBatch();

        batch.AddExportedValue<IWindowManager>(new WindowManager());
        batch.AddExportedValue<IEventAggregator>(new EventAggregator());
        batch.AddExportedValue(container);

        container.Compose(batch);
    }

    protected override object GetInstance(Type serviceType, string key)
    {
        string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
        var exports = container.GetExportedValues<object>(contract);

        if (exports.Count() > 0)
            return exports.First();

        throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
    }

    protected override IEnumerable<object> GetAllInstances(Type serviceType)
    {
        return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
    }

    protected override void BuildUp(object instance)
    {
        container.SatisfyImportsOnce(instance);
    }
}
Jul 7, 2010 at 2:24 PM

Thanks.

I look forward to your post.

That looks a bit better than my reflection hack to get mef hooked up:

 

protected override object GetInstance(Type service, string key)
{
    Type CompositionContainerT = typeof(CompositionContainer);
    MethodInfo mi = CompositionContainerT.GetMethod("GetExportedValue",new Type[]{});
    MethodInfo miConstructed = mi.MakeGenericMethod(service);
    return miConstructed.Invoke(_IoC_Container, null);
}
My next question/thought. (if you have time and thoughts)
I am using MEF's DeploymentCatalog to load xap's at runtime. Now, those xaps contain Views and models. I can get to the models using MEF easily, but my assumption that Micro would use its internal IoC to get to the views was wrong. 
The bootstrapper does give you an opportunity to specify in what assemblies the ViewLocator should go look for these views, but I thought it would make more sense for the ViewLocator to use its IoC to find them. 
The ViewLocator does implement a GetOrCreateViewType but does not use it to hook up views and models. I was wondering why this was so?

 

Coordinator
Jul 7, 2010 at 2:40 PM
The reason why it doesn't use IoC to lookup Views is basically related to the fact that it complicates the naming conventions scheme. It should be relatively easy to replace the implementation with one that uses IoC. Personally, I don't use IoC for Views, so it's never been an issue for me.
Coordinator
Jul 7, 2010 at 2:42 PM
Also, you can use the AssemblySource.Instance to add additional assemblies at any point in time, such as after you dynamically download a module. This basically tells Micro what the "new places to inspect" are.
Jul 7, 2010 at 2:49 PM
Edited Jul 8, 2010 at 6:33 AM

I think I understand better what is going on there now.

The ViewLocator gets the View's Type information from the AssemblySources, which it needs to power IoC to get the view. In other words it does actually use IoC to get the view but it needs the type information first.

It all makes sense now.

Thanks.

[EDIT]

Getting my hands on the loaded assembly is turning out to be problematic.

Jul 8, 2010 at 9:56 AM

Success!

I have customized my bootstrapper to support MEF now and everything works.

Looking forward to your next post on "customizing the bootstrapper" to see how far off the mark I was.

 

Coordinator
Jul 8, 2010 at 12:56 PM
I was mostly planning to show the code above with some explanation. Did you have some other things you did with MEF that you want feedback on? Please feel free to post it here. If it's broadly applicable I can include it the next article.
Jul 12, 2010 at 2:27 PM
Edited Jul 12, 2010 at 4:18 PM

I saw your bootstrapper implementation and it was perfect.

The only addition I had to make was to overwrite the default behaviour of the ViewLocator's LocateForModelType function to support hooking up views with their viewmodels, which are located in downloaded xaps,

 

 

public static Func<Type, DependencyObject, object, UIElement> LocateForModelTypeBase = ViewLocator.LocateForModelType;

 

 

public Bootstrapper()
            : base()
{
    _IoC_Container = DeploymentCatalogService.Initialize();
    ViewLocator.LocateForModelType = LocateForModelType;
}

private UIElement LocateForModelType(Type modelType, DependencyObject displayLocation, object context)
{
    var view = LocateForModelTypeBase(modelType, displayLocation, context);

    var viewTypeName = modelType.FullName.Replace("Model", string.Empty);
    if (context != null)
    {
        viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
         viewTypeName = viewTypeName + "." + context;
     }

     if (view.ToString() == viewTypeName)
         return view;

     view = _IoC_Container.GetExportedValue<object>(viewTypeName) as UIElement;

     return view == null
              ? new TextBlock { Text = string.Format("{0} not found.", viewTypeName) }
              : view;
}

 

In your article about customizing the bootstrapper(http://caliburnmicro.codeplex.com/wikipage?title=Customizing%20The%20Bootstrapper&referringTitle=Documentation) you mentioned: "Also, if you are dynamically loading modules, you’ll need to make sure they get registered with your IoC container and the AssemblySoure.Instance when they are loaded." I could not figure out how to do this. I load my XAPs using MEFs DeploymentCatalog service, but I have no idea how to extract the assembly information needed to power AssemblySoure.Instance, from MEF.

I would be interested in your take on this situation.

 

Jul 15, 2010 at 3:50 PM

Is there a possibility to clear CompositionContainer?

For example, I need to implement Logout command, so all view models and views have to be really destroyed.

However, using MEF they are all cached somewhere in CompositionContainer :(

Is there any way to clean up/replace/recompose default CompositionContainer?

Coordinator
Jul 15, 2010 at 4:52 PM

I'm not sure. Definitely post your this question to the MEF forum and let us know what you find out.