adrift in the sea of experience

Tuesday, March 1, 2011

MEF attribute-less registration

The Managed Extensibility Framework has shipped in .NET4, but the work hasn't stopped: the recent MEF2-Preview3 release added some interesting stuff. Let's take a look at attribute-less registration.

First, a recap of the existing attribute-based registration mechanism (or "Attributed Programming Model" in MEF-parlance):



A composition container can take different kinds of export providers. The diagram shows the catalog-based export provider. TypeCatalog is the most important catalog implementation: it is based on a list of types, which it inspects via reflection for MEF attributes. The other implementations are there for convenience, to quickly build type catalogs out of an assembly (AssemblyCatalog), an entire directory (DirectoryCatalog), or other catalogs (AggregateCatalog).

Here is an illustration of the above with a quick code example featuring some dummy types. (The CatalogExportProvider is not visible because the CompositionContainer can conveniently construct it for us if we directly pass it a catalog.)
interface IFoo { }
   interface IBar { }

   [Export(typeof(IFoo))]
   class Foo : IFoo
   {
      [Import(typeof(IBar))]
      public IBar Bar { get; set; }
   }

   [Export(typeof(IBar))]
   public class Bar : IBar { }

   class Program
   {
      static void Main(string[] args)
      {
         var catalog = new TypeCatalog(typeof(Foo), typeof(Bar));
         var container = new CompositionContainer(catalog);
         var foo = container.GetExportedValue<IFoo>();
      }
   }

For an attribute-free alternative to the above, you might expect that the new MEF release would contain an alternative ExportProvider or perhaps a new ComposablePartCatalog implementation, which would take its information from configuration in code instead of reflection. Surprisingly, that's not how it was done!

In reality, the attribute-less registration is implemented by adding information right above the reflection level! We essentially inject fake reflection information into our catalogs to simulate the presence of attributes. The attribute-free version of the above example looks like this:

interface IFoo { }
   interface IBar { }

   class Foo : IFoo 
   {
      public IBar Bar { get; set; }
   }

   class Bar : IBar { }

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

         registration.OfType<Foo>()
            .ImportProperty<IBar>(property => property.Name == "Bar")
            .Export<IFoo>();

         registration.OfType<Bar>()
            .Export<IBar>();

         var catalog = new TypeCatalog(
             types: new Type[] { typeof(Foo), typeof(Bar) },
             reflectionContext: registration);
         var container = new CompositionContainer(catalog);
         var foo = container.GetExportedValue<IFoo>();
      }
   }

Note how the TypeCatalog takes a new ReflectionContext parameter; that's were the reflection info is injected. An interesting result of this approach is that you can leverage the existing catalogs to register types in bulk. For example, the following example will export all the types which implement IPlugin in a given directory, without using any attributes:
var registration = new RegistrationBuilder();
registration.Implements<IPlugin>().Export<IPlugin>();
var catalog = new DirectoryCatalog("./plugins", "*.dll", registration);

This is just what I've gleaned from a quick peek at the unit tests in the new MEF release. There is much more to the RegistrationBuilder API which I haven't shown here. I'm sure there will be more documentation on the MEF codeplex site shortly. Also, keep in mind that the APIs in preview releases are subject to change.

UPDATE: The MEF product manager, Hamilton Verissimo (aka "hammet" and "haveriss"), has blogged about MEF's convention model.