A Crash Course on the MEF Primitives
With the Managed Extensibility Framework (MEF), you can use Import and Export attributes to declare what a class consumes and what it offers. For example, below is an example of two different shapes and a toolbox that imports all available shapes.
[Export(typeof(Shape))]
public class Square : Shape
{
// Implementation
}
[Export(typeof(Shape))]
public class Circle : Shape
{
// Implementation
}
[Export]
public class Toolbox
{
[ImportMany]
public Shape[] Shapes { get; set; }
// Additional implementation...
}
Generally, to use these components, you will create a catalog which includes them, create a container hooked up to the catalog, and then request a root object from the container, like this:
var catalog = new AssemblyCatalog(typeof(Square).Assembly);
var container = new CompositionContainer(catalog);
Toolbox toolbox = container.GetExportedObject<Toolbox>();
When you request an item from the container, it finds it in the catalog, looks for any imports it has (and the imports of its imports, and so on), creates the objects and wires up the graph as specified by the imports and exports, and returns the object you requested.
The MEF Primitives
Based on the above summary, it may seem like a MEF catalog is simply a collection of types that the container can use. However, catalogs actually provide the available components to the container through an abstraction layer which we call the primitives. The primitives provide APIs for describing, creating, and using components.
An implementation of the primitives specifies how components are defined, and is called a programming model. Different catalogs can provide components that are defined using different programming models. The default programming model for MEF is the attributed programming model which uses the Import and Export attributes. The TypeCatalog, AssemblyCatalog, and DirectoryCatalog that ship with MEF use this programming model.
The ability to use a different programming model provides a lot of flexibility. Instead of using the Import and Export attributes, you could have your imports and exports defined based on a convention or an external configuration file. You could also write a catalog where the components are defined using a dynamic language such as IronPython or IronRuby. And you can use an AggregateCatalog to combine all of these catalogs and use them all together in the same container.
MEF Primitives Definitions
ComposablePartDefinition and ComposablePart are the MEF primitives classes which represent a component. The relationship between a definition and a part is similar to the relationship between a type and an instance. A part represents an instance of a component. A part can be created from a part definition using the CreatePart() method. In contrast to CLR types, however, a ComposablePart does not need to have a corresponding ComposablePartDefinition.
The exports and imports of a part or part definition are represented by ExportDefinitions and ImportDefinitions:
public abstract IEnumerable<ExportDefinition> ExportDefinitions { get; }
public abstract IEnumerable<ImportDefinition> ImportDefinitions { get; }
An export definition has a ContractName property which specifies the contract which is being exported, and a Metadata property (which is a dictionary of string to object) which can store additional information about the export.
An import definition specifies what exports can be used to satisfy the import. It does this using a constraint, which is an expression that can be evaluated on an export definition to determine if a given export can satisfy the import.
public virtual Expression<Func<ExportDefinition, bool>> Constraint { get; }
Nicholas Blumhardt has written a blog post about the import being represented as a constraint which covers this in more depth. The import definition also has properties which specify whether the import supports recomposition and its cardinality. The cardinality specifies how many exports can be used to satisfy the import, and can be ZeroOrMore (for collection imports), ExactlyOne (for required single-valued imports), or ZeroOrOne (for optional single-valued imports).
ComposablePart
In addition to the export and import definitions, a composable part provides functionality for setting the part’s imports and getting its exports.
public abstract void SetImport(ImportDefinition definition, IEnumerable<Export> exports);
public virtual void OnComposed();
public abstract object GetExportedValue(ExportDefinition definition);
SetImport is used to set the imports for a part. The first argument is the import definition of the import to set, and the second argument is a list of exports to satisfy the import. The Export class used here contains the same contract name and metadata information that an export definition would have, but it also has a GetExportedValue() method to return the actual value of the export.
OnComposed will be called after all of the imports have been satisfied. After that, GetExportedValue may be called to get the exported value for an export (specified by the ExportDefinition). If any imports are recomposable, SetImport and then OnComposed will be called again if the available imports change.
Custom Catalogs and Custom ExportProviders
To create a custom programming model, create your own part definition and part classes which derive from ComposablePartDefinition and ComposablePart. You may also need to create your own implementations of ImportDefinition and ExportDefinition. Then create a catalog class which derives from ComposablePartCatalog, and override the Parts property to return a collection of your custom part definitions.
Writing a programming model from scratch can be quite complex. If you want a programming model similar to the default MEF model, but where the imports and exports are defined in different ways (such as through convention or configuration), you may be able to use the methods in the static ReflectionModelServices and AttributedModelServices classes to simplify this.
If you simply want to provide exports to the MEF container, but the exports don’t correspond to parts that have any imports that need to be satisfied by MEF, you can simply implement a custom ExportProvider. This might be useful if you want services from an existing service locater to be available to MEF parts, or if you want to supply configuration values stored in a file.
Further Information
For more information on MEF and the primitives, see the MEF CodePlex site. In particular you may want to read the Architecture Overview and the Hosting the .NET Composition Primitives whitepaper. And of course feel free to ask questions on the MEF Forums.
Comments
Anonymous
June 09, 2009
Daniel just wrote a really nice post explaining the basics of MEF primitives . I recommend this to allAnonymous
June 09, 2009
Disclaimer: This is a prototype, not production ready code. It is more illustrative of what you can doAnonymous
June 09, 2009
Disclaimer: This is a prototype, not production ready code. It is more illustrative of what you can do