MEF’s Convention model

In the last drop of MEF on codeplex we introduced a handful of interesting features. One of them – which has a large API surface – is the new registration mechanism. It allows you to accomplish interesting things:

  • Define a predicate (rule) that will cause all matching types to be exported as MEF parts
  • Export types that you do not have access to the source code
  • Bridge an existing plugin model – say, FxCop’s – to MEF’s API

Using it is quite simple. Suppose you have the following types in a project:

     public interface IController
    {
    }

    public class AccountController : IController
    {
    }

    public class HomeController : IController
    {
    }

You can then decide the export AccountController and HomeController based on something they share: they both implement IController. (Additionally, they both are concrete classes whose type name ends with ‘Controller’, and you could also define a rule based on this).

 using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;

class Program
{
    static void Main(string[] args)
    {
        var registration = new RegistrationBuilder();

        registration.Implements<IController>().
            Export<IController>();

        var catalog = new AssemblyCatalog(
            typeof(Program).Assembly, registration);
        var container = new CompositionContainer(catalog);
    }
}

So we build a RegistrationBuilder and tell it to get all types that implement IController, and export those types with the IController contract.

Note though, that the RegistrationBuilder is _not_ a catalog. It is passed to a catalog – in the example above, an AssemblyCatalog. So the universe of types applicable to this RegistrationBuilder is limited to the set of types that this AssemblyCatalog can “discover”.

If you’re unsure it’s working, you can set a breakpoint in your code and investigate the Parts property of the catalog. It should have two “Parts”: HomeController and AccountController, both exporting a single contract: IController.

Exporting Self

We could make a small change to have them exporting more than the IController contract.

         registration.Implements<IController>().
            Export(). 
            Export<IController>();

The single .Export will cause the type to be exported as “itself”. It’s the equivalent of just adding a [Export] attribute to a type, whereas the other line – Export<IController> – is the equivalent of [Export(typeof(IController))]

Configuring the export

You’d also notice that some of the API takes an optional configuration instance wrapped in an Action delegate. Export() takes ExportBuilder. Import() takes ImportBuilders. You can use it to add more stuff to the Export/Import such as metadata.

             registration.Implements<IController>().
                Export(config => config.AddMetadata("name", t => t.Name)).
                Export<IController>();
  

Where’s the convention?

Interesting question. Our piece of code will select all types matching the predicate (in this case, Implements<> is a shortcut to a predicate). We don’t know how many will match: 1, 2, 100 controllers? Who knows?

It’s not reasonable to think that all these controllers would look the same. First and foremost, some are bound to use constructors. What should you do?

Well, nothing. By convention our RegistrationBuilder will select the constructor with most parameters. Also, if something looks like a collection, it will change the import cardinality to Zero or Many – which is the equivalent of using the [ImportMany] attribute.

Sweet, isn’t it?

Bypassing the convention

What if? What if you want to use a different constructor for a specific controller? What if a controller happens to have two constructors with the same number of arguments?

In this case, reject the convention by being explicit. Suppose your AccontController looks like the following:

 public class AccountController : IController
{
    private readonly IMembershipProvider _provider;
    private readonly IAuthentication _auth;

    public AccountController(IMembershipProvider provider)
    {
        _provider = provider;
    }

    public AccountController(IAuthentication auth)
    {
        _auth = auth;
    }
}

In this case, add the following registration code specifically to the AccountController. Notice that it selects the constructor based on a Expression<>. We thought this is great as it’s refactor-friendly.

     registration.OfType<AccountController>()
        .SelectConstructor(builder => 
            new AccountController(
                builder.Import<IMembershipProvider>()));

The first parameter your lambda takes is a ParameterImportBuilder. Its Import methods allows you to further configure the import as well..

Explicit wiring

This is a recurring question: “I have two Foo’s. My piece of code imports just one. Mef blows up. What should I do?”

MEF rightfully throws an exception in this case, since it cannot tell which Foo it should give to the importer of a single one. But what to do about it? Well, building on our last example, imagine that AccountController takes a single IAuthentication instance, but you have two available:

 
    registration.OfType<DummyAuth1>()
        .Export<IAuthentication>();
            
    registration.OfType<DummyAuth2>()
        .Export<IAuthentication>();
        
    ...
    
    public class AccountController : IController
    {
        private readonly IAuthentication _auth;

        public AccountController(IAuthentication auth)
        {
            _auth = auth;
        }
    }

Our solution to this problem is to “name” an specific export and then use this “name” on the import side as well.

 
    registration.OfType<DummyAuth1>()
        .Export<IAuthentication>(
           builder => builder.Named("my_1") );
            
    registration.OfType<DummyAuth2>()
        .Export<IAuthentication>();

    registration.OfType<AccountController>()
        .SelectConstructor(builder => 
            new AccountController(
                builder.Import<IAuthentication>(
                    config => config.Named("my_1") )));

 

This was a quick overview of this feature. There’s more to come, let me know what you’d think.