Is MEF a DI Container?
A couple of weeks ago I read this article in Code Magazine about dependency injection (DI), and they mentioned the Managed Extensibility Framework (MEF) as a DI container. I never thought of MEF as DI container, so I thought, let’s take it for a spin.
Managed Extensibility Framework
So Microsoft coins MEF as a library for creating lightweight, extensible applications. It provides developers the tools for discovering and using extensions with no configuration required. Developers creating extensions are given tools for easily encapsulating code and avoiding hard fragile dependencies.
MEF is providing support for components to discover other components implicitly, via composition. A MEF component, called a part, declaratively defines its imports and exports. The imports are its dependencies, while the exports are its capabilities that the components makes available. When a part is created the MEF composition engine will resolve its imports, with what is available from other parts. Doing this declaratively makes them discoverable at runtime, this avoids hard-coded references and fragile configuration files. By adding metadata MEF allows tagging of extensions for querying and filtering. Since there is no hard dependency on a particular assembly, extensions can be used across multiple assemblies.
So lets take a look at simple example. I’ve created a simple calculator to display the basics of MEF. You can find the source code at GitHub.
The Program class holds an ICalculator property:
1: [Import(typeof (ICalculator))]
2: private ICalculator Calculator;
This means that the Program class requires one, exactly one, implementation of ICalculator. Every import has a contract, which determines what exports it will be matched with. In this case I have specified it explicitly with a string.
The calculator class is declared with the Export attribute with type of ICalculator, saying that this class offers an implementation of the ICalculator interface:
1: [Export(typeof(ICalculator))]
2: class Calculator : ICalculator
In order for Calculator to be extensible, it needs to import a list of operations. An ordinary Import attribute is filled by one and only one Export attribute. If more than one is available, the composition engine produces an error. To create an import that can be filled by any number of exports, you can use the ImportMany attribute:
1: [ImportMany]
2: IEnumerable<Lazy<IOperation, IOperationData>> operations;
Lazy<T, TMetadata> is a type provided by MEF to hold indirect references to exports. Here, in addition to the exported object itself, you also get export metadata, or information that describes the exported object. Each Lazy<T, TMetadata> contains an IOperation object, representing an actual operation, and an IOperationData object, representing its metadata.
So lets implement the first operation:
1: [Export(typeof(IOperation))]
2: [ExportMetadata("Symbol", '+')]
3: class Add : IOperation
4: {
5: public int Operate(int left, int right)
6: {
7: return left + right;
8: }
9: }
Again, the Add class exports an implementation of the IOperation interface. Additionally I’ve added the ExportMetadata attribute. This attribute will attach metadata, in the form of a name-value pair, to this export.
Finally, a small piece of calculator logic:
1: public string Calculate(string input)
2: {
3: int left;
4: int right;
5:
6: var fn = FindFirstNonDigit(input);
7: if (fn < 0)
8: {
9: return "Could not parse command.";
10: }
11:
12: try
13: {
14: left = int.Parse(input.Substring(0, fn));
15: right = int.Parse(input.Substring(fn + 1));
16: }
17: catch (Exception)
18: {
19: return "Could not parse command.";
20: }
21:
22: var operation = input[fn];
23:
24: foreach (var i in operations.Where(i => i.Metadata.Symbol.Equals(operation)))
25: {
26: return i.Value.Operate(left, right).ToString(CultureInfo.InvariantCulture);
27: }
28:
29: return "Operation Not Found!";
30: }
To wire things up MEF uses a CompositionContainer:
1: private CompositionContainer container;
To register the necessary types, you need to create a catalogue of types. This is usually done at the assembly level using the AggregateCatalog:
1: var catalog = new AggregateCatalog();
2: catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
3: container = new CompositionContainer(catalog);
And composing the MEF parts:
1: try
2: {
3: container.ComposeParts(this);
4: }
5: catch (CompositionException exception)
6: {
7: Console.WriteLine(exception.ToString());
8: }
This wraps up my overall introduction to MEF, but does this qualify MEF as a DI container?
Is It a DI Container…?
This post is not suppose to be a complete outlining of what dependency injection is, or how a DI container works. People have written books on these matters. Please see my previous post on DI. I’ll therefore be very concise, and focus on how MEF fits into the DI world.
MEF was developed for a different purpose than a DI container. It’s a framework facilitating add-in support for parts. Such parts is an unknown component for a standard application. A part exposes certain interfaces, but that is all the application knows about the part. There may be zero, or multiple parts, depending on the environment. This is a key difference from a DI container, where we typically know about all, or most of, the components at compile time. We use the knowledge about components to configure the container in the applications Composition Root.
Facilitating add-in requires some sort of discoverability mechanism. Usually discoverability is implemented by scanning a certain folder for assemblies to find all classes implementing the required abstraction. MEF addresses through an advanced discovery model that uses attributes to define consumers and their services.
So to sum up. A fully-fledged DI container favors decoupled composition of services. This provides the greatest degree of flexibility, but it comes at a cost. As developers, we must have knowledge about the components we wish to compose at the time we configure the container. MEF favors discovery of components. This addresses the issue when we know little about the add-ins a design time. The tradeoff is that the discovery mechanism is tightly coupled with the components, so we lose some flexibility.
Some proponents of MEF argues it’s a DI container, but as it, it is not. You might be able to use is as such, but out of the box it a framework for facilitating a plugin architecture.
Tweetcomments powered by Disqus