Introduction
MEF is new a library that enables extensibility to be added to an application. Extensibility isn’t a new concept, but MEF is an attempt by Microsoft to provide a single architecture across the .NET platform to enable flexible development of extensibility without having to roll your own code, or having to learn a completely new architecture when writing a plug-in for another application.
The other thing to note is that MEF is more than a CodePlex project – it is slated to be a part of .NET Framework v4. The latest previews have focused on making the libraries more developer friendly. The preview versions are available from the Codeplex site.
Using MEF in your Application
To get started using MEF, grab the latest drop from this page (Preview 5 at the time of writing this) and add a reference to System.ComponentModel.Composition.dll in your project.
Declaring Extensions
As a minimum, the class needs the Export attribute and a reference to the MEF assembly. The code snippet below shows the current contract attributes available (more than one Export attributes can be used on a class).
1: using System.ComponentModel.Composition;
2:
3: [Export]
4: [Export(typeof(IExtension))]
5: [Export("ExportTypeFoo")]
6: [Export("ExportTypeFoo", typeof(IExtension))]
7: public class MyExtension : IExtension
8: {
9: // stuff here
10: }
Use the first attribute if you are looking to use this type specifically. I’m not as sold on the value of this, as it seems excessive to use MEF to find something I already have referenced – perhaps I haven’t found the right use for it yet.
The second attribute is good if you have a defined interface which all extensions should implement. If you arrange your application so that the core (interfaces, business logic) is separate from the UI, then you can write extensions in a separate assembly which references the core (or a subset containing the interfaces), and then add the extensions assembly into a predefined folder to make it available at runtime.
The third attribute uses a string to define the contract type. This still suffers from the problems that come from using the first attribute, but I suppose its there for those times when you just don’t want to define interfaces.
The fourth attribute is for interfaces with a specific contract. For example, if you have an editor which allows extensions to be written to manipulate a text file. The editor supports right-click on selected text to run all available extensions, but also has a menu item with sections to group the extensions (eg Format, Transform, Validate). Rather than having multiple interfaces for each menu type, the grouping name and the interface becomes the contract type.
Consuming Extensions
One of my habits with consuming extensions is to wrap the functionality in a separate class. The code below shows the minimum that you need to configure a list of extensions:
1: using System.ComponentModel.Composition;
2: using System.ComponentModel.Composition.Hosting;
3:
4: internal class BasicExtensionManager
5: {
6: [ImportMany(typeof(IExtension))]
7: private List<IExtension> allExtensions = new List<IExtension>();
8:
9: public void Compose()
10: {
11: allExtensions.Clear();
12: var catalog = new AssemblyCatalog(Assembly.GetAssembly(typeof(IExtension)));
13: var container = new CompositionContainer(catalog);
14: var batch = new CompositionBatch();
15: batch.AddPart(this);
16: batch.AddExportedObject<AssemblyCatalog>(catalog);
17: container.Compose(batch);
18: }
19:
20: public List<IExtension> Extensions
21: {
22: get
23: {
24: return allExtensions;
25: }
26: }
27: }
I’ve used the ImportMany attribute here to obtain multiple extensions (previous versions had this as an implicit behaviour of Import but this has been separated out with Preview 5). You also have access to the Import attribute if a single entity is required (for example, the catalog contains multiple items and you allow the user to select one, which is loaded into the application).
A few terms to note in the above snippet:
Catalog: the catalog can be an AssemblyCatalog, DirectoryCatalog or TypeCatalog. The catalog searches any found assemblies for the Export declarations mentioned above.
CompositionBatch: the Composition Batch is used to represent the “session” when using extensions. In this case I supplied the extension manager (which has a property with the Import declaration) and the catalog containing the extensions (which have the Export attributes as shown above). If the catalog is updated, the batch handles this (previously this had to be handled manually).
CompositionContainer: handles the mapping of import and export objects. You can query the exported items here, or simply pass a CompositionBatch and do it automatically.
The code to use this extension manager is shown below:
1: BasicExtensionManager mgr = new BasicExtensionManager();
2: mgr.Compose();
3: foreach (var e in mgr.Extensions)
4: {
5: Console.WriteLine(e.ToString());
6: }
Demo: Single Instance Plugins
To drill down deeper on the importing of extensions, I thought I’d implement an extension manager that a) provides information about the types imported and b) creates instances of each available type for an application to use. For this sample, I have two extensions which are added to an application. ExtensionA can be created in multiple instances, but ExtensionB can only be used in a single instance. But how do we set this behaviour with MEF?
The extension author has control over how their extension is used, so they can specify whether or not their code can exist in multiple instances. The trick is to use the new PartCreationPolicy attribute.
The options to provide with this attribute are:
NonShared: A new instance is created for each type.
Shared: Only one instance of this type should exist at any time.
1: [Export(typeof(IExtension))]
2: [PartCreationPolicy(CreationPolicy.NonShared)]
3: public class ExtensionA : IExtension
4: {
5: // implementation
6: }
7:
8: [Export(typeof(IExtension))]
9: [PartCreationPolicy(CreationPolicy.Shared)]
10: public class ExtensionB : IExtension
11: {
12: // implementation
13: }
The consumer can also specify the creation policy for any imported extensions. This code snippet shows how the import can request extensions that are only single-instance:
1: [ImportMany(typeof(IExtension), RequiredCreationPolicy=CreationPolicy.Shared)]
To test this behaviour I used a Guid property on each extension. This is generated the first time it is read (there are other ways create a unique identifier, the Guid was was quick and relatively straightforward). I wrote a different implementation of the Extension Manager that uses this code snippet to create a new instance of a specific type. The container contains the catalog with the available extensions.
1: public IExtension CreateNewInstance(Type t)
2: {
3: var result = container.GetExportedObjects<IExtension>()
4: .Where(c => c.GetType() == t).SingleOrDefault();
5: // may get null if you pass an invalid type
6: return result;
7: }
Now, when I run through the test suite and request multiple instances of each extension type, I get this resulting output. Extension A has a different identifier for each instance, and each instance of Extension B is simply a reference to the first instance.
Hooray!
Try it out
The sample I have included below has both extension managers and the tests used above. I’m aiming to have something more complex up next (need to polish the concept some more and do some refactoring of the application itself) but hopefully this is something that can be used as a starting point. The MEF assembly is included with this zip file.