MEF is kind of a dependency injection framework for .Net, I deliberately say “kind of”, because MEF is much more. To get an idea about what MEF does for you, take a look at the code below:
You can see three properties of three different interface types. The idea is, that interfaces provide a very loose coupling between my Printer class and the classes implementing these interfaces, but somewhere in my code, the constructor of an implementing class must be called. This means that when I want to switch the implementing class, I need to change the constructor call. MEF removes this dependency between my class and an implementing class completely. It does this by injecting an instance of the implementing class into the properties. Because MEF does the injecting, I never have to call any constructor and the dependencies between my Printer class and any implementing classes are completely removed. The class responsible for the injecting is the CompositionContainer class. The injecting is happening on the lines 45 and 46. A CompositionContainer needs a ComposablePartCatalog, in this case the AssemblyCatalog. A catalog is in essence a catalog of classes that can be injected. MEF out of the box supports a couple of different catalogs, I’m using the AssemblyCatalog, which looks in the given Assembly for types that it can inject, but there is also a TypeCatalog and a DirectoryCatalog. The problem of these built in catalogs is that they rely on attributes in the source code for determining whether a type can be injected:
This is the type that gets injected in the GoodByer property. When I call “c.ComposeParts(this)” on line 46, a couple of things happen:
- The supplied object’s (this) properties with the Import attribute are enumerated.
- A contractname is created, this is very important. Default with the import attribute, the contractname is the fully qualified type name of the property to which the Import attribute is applied. In this case for the Goodbyer property: “InterfaceLibrary.ISayGoodBye”.
- A search begins for a type which exports the same contractname. Where this search is performed is dependant on which type of catalog is used. When a type is found with an Export attribute with a matching contractname, an instance of this type is injected into the property. It is important to realize that the matching of imports with exports is purely based on strings and not interface types. It works with interface types because of the fully qualified name.
A much heard criticism of the built in catalogs is this attribute based programming model. For the imports, it isn’t that bad, but for the exports it is. When I want to switch the implementation that is injected to an interface typed property, I need to change the Export attributes of the concrete types. Changing attributes, means recompiling. It would be nice if I could configure in an external file which types get injected. Luckily, MEF has some extensibility points. I could write a custom catalog and most people would do this. But in the newer versions of MEF (Visual Studio 2010 Beta 2), ExportProviders were introduced. With an export provider you can determine where and how is searched for classes that can be injected. I decided to create a LinqToXmlExportProvider class, which uses an external XML file to determine which classes are exported and what their contractname is. This way I can get rid of the pesky Export attribute. First take a look at the external exports.XML file:
It’s a very simple file which just says to the ExportProvider: “if you’re looking for a contractname of “InterfaceLibrary.IGreeter ”, “ImplementationLibrary.HelloGreeter,ImplementationLibrary” is the type you can inject. This file replaces the Export attributes. Notice that to specify the type to inject, the assembly qualified name is used. Let’s take a look at the class which parses this XML file and uses it to provide exports to MEF:
I’m going to cover the important parts:
- Line 15 – 21: This is the constructor. It get’s the location of the file and parses it using Linq to Xml into a Dictionary. The contractname is the key and the type of which an instance can be injected is the value. This allows me to easily give MEF an instance to inject when MEF gives me a contractname.
- Line 23 – 36: This is the heart of the class and where it all happens. When MEF finds a property with the Import attribute , it creates an ImportDefinition. In this definition, the contractname and optionally metadata is saved. The GetExportsCore() method is called by MEF and MEF supplies an ImportDefinition. It is my task to give MEF according to this ImportDefinition, an IEnumerable of exports (read: instances that can be injected) that match the ImportDefinition, in this case, that match the contractname. This the responsibility of the GetExportsCore() method.
- Line 27: I’m checking whether the XML file provided a type to inject according to the contractname.
- Line 29-30: If I have found a type belonging to the supplied contractname, I instantiate it using reflection and the default constructor.
- Line 31: If an ImportDefinition is the ImportAttribute, you can see the ExportDefinition as the Export attribute. An ExportDefinition contains information about the exported type, normally this is taken from the Export attribute, but in my case from the XML file. This information is the exported contractname and possibly some metadata. I don’t use metadata so I just supply an empty Dictionary.
- line 33: an Export object combines an ExportDefinition with an actual instance of the type the ExportDefinition belongs to. The Export object is added to the list and finally returned. Notice that MEF supports injecting of IEnumerables when multiple types can satisfy an import. I don’t support this, so my returned IEnumerable always contains one item, or it is empty when no class can be found that satisfies the ImportDefinition.
That’s it, that is all there is to it. Using this ExportProvider is easy, just supply it to the constructor of the CompositionContainer:
Now I only have to change the XML file to determine which type gets injected when MEF gives me a contractname, so I can switch the implementing type of the GoodByer property and all the other interface based properties, without recompiling . You can find a working sample project here.