Tuesday, February 24, 2009

MAF and MEF

This morning I listened to Glenn Block's MEF talk on Hanselminutes, wherein he touched on the differences between MAF and MEF. I was glad he did. Not only are their names confusingly similar, but their goals seem so on the surface, too.

Glenn sums up the differences well by saying that MEF is about extensibility whilst MAF is about isolation. Moreover, they are not mutually exclusive. It is entirely possible - and legitimate - to use both together.

Indeed, this is something I've been meaning to try and I will cover it in this post. Specifically, I will show how a MAF add-in can use MEF to compose a set of unknown parts to do its job.

First of all, I started with my skeletal MAF solution. I'm really glad I took the time to put that thing together - it has saved me a lot of time with these MAF-related blog posts ;)

Next up, I grabbed the latest preview of MEF from the MEF CodePlex site. I copied the System.ComponentModel.Composition assembly into my skeletal solution and referenced it from my sample add-in. MEF will eventually be part of the framework (version 4.0 is the target) but for now it is standalone.

Then, I defined the interface my add-in will rely on (import):

public interface IMessageProvider
{
    string Message { get; }
}

It will import any implementations of this interface it finds and print out the corresponding messages. I added one implementation of this interface to the add-in project:

[Export(typeof(IMessageProvider))]
public class SampleMessageProvider : IMessageProvider
{
    public string Message
    {
        get { return "A message provider in the same assembly as the add-in."; }
    }
}

I added a Compose() method to the add-in:

private void Compose()
{
    var directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    var container = new CompositionContainer(new DirectoryCatalog(directory));
    var batch = new CompositionBatch();
    batch.AddPart(this);
    container.Compose(batch);
} 

This method is called by the add-in when it is initialized. It composes the MEF container by looking for all parts in the directory containing the add-in. The easiest way to get that directory was to use the location of the currently executing assembly, since that assembly is the add-in itself.

The add-in imports all message providers:

[Import]
public IEnumerable<IMessageProvider> MessageProviders { get; set; } 

And then it iterates over them during its Initialize() method:

public void Initialize(IHost host)
{
    Compose();
    Console.WriteLine("This is the sample add-in, running inside host with name '{0}', version {1}. AppDomain name is '{2}'.", host.Name, host.Version, AppDomain.CurrentDomain.FriendlyName); 

    foreach (var messageProvider in MessageProviders)
    {
        Console.WriteLine(messageProvider.Message);
    }
}

Running the application at this point works just fine and we see the single message provider was found:

image

Now I wanted to add another message provider in a completely different assembly to the add-in. So I added a new project called MessageProviders. This project references both the System.ComponentModel.Composition assembly as well as the add-in assembly (so it can use the IMessageProvider type). Importantly, the add-in assembly does not reference the MessageProviders assembly. The MEF catalog ties the two together for us. Also, you could imagine that the IMessageProvider interface could be in a standalone assembly, so that the add-in assembly and MessageProviders assembly don't need to reference each other.

I added another message provider to the new project:

[Export(typeof(IMessageProvider))]
public class ExternalMessageProvider : IMessageProvider
{
    public string Message
    {
        get { return "A message provider in an external assembly."; }
    }
}

Then I ensured the build script copied the output of this new project to the add-in's directory. I hit F5 and things just worked:

image

Both message providers were found by MEF running in the context of a MAF add-in.

To me, one of the benefits of achieving isolation with MAF is that add-in authors are free to use whatever technology they want to meet the needs of their add-in. If they need dependency injection, the host has not imposed on their choice. They can use Unity, StructureMap, autofac, ObjectBuilder or whatever best suits them. And if they need composition, they can use MEF.

1 comment:

Olinzer said...

Hi! This is what I was looking for! You have a very helpful and amazing blog. I've just started with MAF, and as you posted before, the system.addin is hard to understand. I'd like to know, if can I ask you many doubts I have. Thanks for all your post, they're great! Keep up the good work. Best wishes from México!