Partager via


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.

Comments

  • Anonymous
    March 08, 2011
    The comment has been removed

  • Anonymous
    March 09, 2011
    Excellent work Hammett!

  • Anonymous
    March 09, 2011
    The comment has been removed

  • Anonymous
    March 10, 2011
    Love it! One question about the MVC scenario... Is per-request lifetime on the cards for this round?

  • Anonymous
    March 10, 2011
    Hey Nick !!! Good to "see" you here Actually not. We have some upcoming scoping feature, though, that should make this and other scenarios easy. Stay tuned - for whoever is going to replace me as MEF pm ;-)

  • Anonymous
    March 15, 2011
    Thanks for the great article! Very helpful. One quick question. I have a repository using generics. public class MyRepository<T> : IRepository<T> where T : IIdentifiable {      private readonly string  _connectionString;      public MyRepository(string connectionString)      {               _connectionString = connectionString;      } } Can you please help me to send the constructor parameters using Convention Model. Thanks

  • Anonymous
    March 15, 2011
    Can you post some full example code that I can run and experiment with?

  • Anonymous
    March 16, 2011
    @Anthony, check the samples included in the latest drop. The MVC sample has a working example. @Kris, something like registration.Implements(typeof(IRepository<>)).Exports(typeof(IRepository<>)) would do the trick for you.

  • Anonymous
    March 16, 2011
    Hammett, what's the syntax for property imports?

  • Anonymous
    March 16, 2011
    Ok, I got props working:            context.OfType<MainWindow>().Export()                .ImportProperty(p=>p.PropertyType == typeof(MainWindowVM));

  • Anonymous
    March 17, 2011
    @glenn, if you're using context.OfType<T>, you should try using the strongly typed PropertyImport/Export instead, which takes an Expression<Func<T, object>>

  • Anonymous
    March 26, 2011
    How can I get the ContractName to map to the specific TypeName using the convention model so that I can use the TypeName in the GetExporedValue method. Currently it is defaulting to the interface name. Say supposing, I would like to register a bunch of plug-ins using a common interface IPlugIn and later would like to get a specific plugin through GetExportedValue or CommonServiceLocator given the name of the plug-in. Thanks

  • Anonymous
    March 28, 2011
    Read above the section on Export Self

  • Anonymous
    March 30, 2011
    @hammett, I've a quick question. How can I use factory pattern through convention model. Thanks.