Component discovery and composition Part 1b: Fundamentals - MEF
Combining MEF With Castle.Windsor For Low-Ceremony Component Composition
In the last part of this series, we looked at the scope of the component discovery and composition problem. This time, we're going to look at MEF and see what it brings to the table.
Part of the solution — MEF
MEF is principally trying to solve the problem of discovering and dynamically composing "parts" without having to take an explicit dependency along the way. In particular, you don't have to take a dependency on the assembly containing the part.
Instead, it has the more abstract concept of a ComposablePartCatalog which describes where to look for components. This might be an assembly (AssemblyCatalog), the assemblies in a particular directory (DirectoryCatalog), the assemblies in a particular XAP (DeploymentCatalog) or perhaps some aggregation of one or more of these (AggregateCatalog).
That deals with where to find parts. But what exactly is a MEF part? Well, "any old CLR type", more or less. What turns a class into a part is that it is exported from the assembly for MEF to find, and then imported by some other type that needs to make use of it.
And how do we achieve this importing and exporting? We decorate our classes with [Export] and [Import] attributes.
Let's imagine we have a class called FireTruck that imports an IHose. First, let's export the hose as a part.
And then we need to import it into the fire truck. We could do that with a public property, but in this case we'll use an importing constructor.
This tells MEF to use this constructor to inject the hose into the truck when it resolves the imports.
The final stage of the process is composition: finding the exports to satisfy the imports. We create a CompositionContainer, giving it a ComposablePartCatalog, and call SatisfyImportsOnce() on it for some root object.
SatisfyImportsOnce() looks in the container for exported components to satisfy the imports needed by our root object, and the imports needed by those imported components and so on, all the way down the tree until everyone is happy.
But that's not the only thing we can do with the container. We can also call GetExports() to find all the exports of a particular service type.
So, MEF isn't just doing the component discovery, and composition, it has also got other features of a regular component container. We can export the part against name rather than a type, for instance. We can explicitly find a component that we've put in the container given only the type it exports, although the syntax is a bit clumsy. We can control the lifetime (but only as a singleton or transient). It is highly extensible, but the in-the-box container functionality is limited. Glenn Block talks about the various ways in which MEF doesn't provide all the bells and whistles of an IOC container in this blog post.
The one which is particularly striking for real-world apps is a lack of out-of-the-box flexibility in configuration. Generic types are very badly supported, and object lifetime control is, as I mentioned, very simple.
A more subtle but possibly more corrosive problem is the need for all those attributes polluting your otherwise pristine CLR types. This kind of ceremony obscures the primary intent of our code, and ideally we'd like to get rid of it (as per problem 4).
So MEF has a nice story for finding components (especially with its support for Silverlight), but it is not such a great IOC container. For that, we can turn to Windsor, which is for the next post.
This code snippet is an example of the 3-calls pattern for composite applications.
- Register components in the container
- Resolve some root object (and all its dependencies)
- Release the container resources
The first two happen once at application start-up and the last at application exit (if you want the objects to be around while you are using them!) In practice there may be one or two other patterns that require you to explicitly resolve another root object — factories and resource mapping being typical problems that require it.
If you haven't come across Lazy<> before, it is a new type in .NET 4 which allows for the lazy initialization of a type – the instance is not created until just before it is used. Similar in form to Nullable<>, you access the actual instance through a Value property.
I've not touched on MEF's extremely powerful support for dynamic re-composition — dealing with the situation where a new component is installed somewhere and everything is re-composed. This is largely because I find the solution very difficult to manage in all but the simplest cases, and the management solution tends to become largely equivalent to re-bootstrapping the application anyway. That said, in well-understood circumstances it is invaluable. Dynamic discovery of a newly installed application plug-in is the canonical example. Glenn Block's Hello MEF Silverlight sample referred to in the "More Reading" section makes use of this feature.