Welcome to MSDN Blogs Sign in | Join | Help

Import Cardinality, and Picking Which Export to Use

In the Managed Extensibility Framework (MEF), an import has a cardinality, which expresses how many exports can be used to satisfy the import.  The possible values are ZeroOrOne, ExactlyOne, or ZeroOrMore, and they can be declared in the following ways:

[Import(AllowDefault = true)]
IService ZeroOrOneImport { get; set; }

[Import]
IService ExactlyOneImport { get; set; }

//	Note that the property here is an array.
//	It could also be IEnumerable<IService> or a concrete type that implements ICollection<IService>.
[ImportMany]
IService[] ZeroOrMoreImport { get; set; }

If the cardinality of the import is ExactlyOne, then there must be exactly one matching export available.  If there are zero, or if there are two or more, then the import will not be satisfied, and the part will either be rejected or the composition will fail.

A common question we get about MEF is how to modify this behavior, either by specifying a default export to use, or by specifying some logic that will pick the best one according to some criteria.  This question recently came up internally at Microsoft, and our architect Blake Stone gave a good explanation and overview of the options.  The rest of this post is based on what he said.

In general, we strongly encourage consumer-side policy on picking one of many for what we believe is a good reason. We're trying to encourage not just individual stand-alone extensible systems, but the development of reusable subsystems. If two different teams develop something useful independently then we'd like to be able to drop both of them into a single product and have them work. Designing with a system-wide policy in mind prevents this from working. A natural workaround is to try to isolate the two subsystems from one another using different containers, but this also leads to problems down the road as subsystems tend to overlap in time - and it becomes awkward for one part to participate in more than one subsystem.

So the recommended approaches look like this:

a) Make the importer aware of the selection policy.  Change the import to an ImportMany (and the corresponding property to a collection), and have the importer choose which export to use from the list.  You can avoid duplicating the code to select the service to use in each importer by writing a custom collection class (which implements ICollection<T>), with an accessor for getting the desired T.  Then you can use [ImportMany(typeof(IService))] PolicyBased<IService> (where PolicyBased<T> is the custom collection you wrote) and let the reusable prioritization mechanism do the work for you. Many different schemes along these lines can co-exist.

b) Use two different contracts. In this instance the importer can go ahead and write [Import] IService and pretend things are simple. An exporter, on the other hand would need to [Export("my.namespace.PrioritizedService")] and another part would import all of the prioritized services, pick one, and export it under the IService type-derived contract.

It's worth keeping in mind that in both cases the selection mechanism is required to produce something meaningful, so you'd have to come up with a default IService implementation.

c) If you really must make all of this transparent per your original request, yes that can also be achieved. Just keep in mind that you're opting out of widespread reuse of the functionality you write that depends on it. What you need to do is define your own custom topology of ExportProviders for a container. Implementing a full-fledged ExportProvider can be tricky, but implementing one as a filter over other full-fledged implementations is relatively straightforward. Our own AggregateExportProvider uses a prioritization scheme of its own along these lines (exporters found earlier in its list are preferred over ones found later for the purpose of satisfying imports of exactly one implementation.) For more information on how to do this, see Glenn Block’s blog post on customizing container behavior with defaults.

Posted by dsplaisted | 0 Comments

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.

Posted by dsplaisted | 3 Comments

MEFGrid: A Sample MEF Application

image

MEFGrid is a sample MEF application that I presented at Seattle Code Camp and at an Olympia .NET user group meeting.  It includes connect 4 and the game of life, but other grid based games could be written and dropped into the extensions directory.

The application demonstrates some of the more advanced features of MEF.  The available life patterns, for example, are stored as text files in the LifePatterns directory.  A custom catalog makes these available as string-based exports via a custom programming model, and the life game uses a contract adapter to convert the string-based exports to command exports that set the current life pattern.

If I can find the time I will write a series of blog posts explaining how the application works in more detail.  For now, feel free to post questions in the comments or on the MEF discussion forums.

Download the MEFGrid sample.

Posted by dsplaisted | 1 Comments

Attachment(s): MEFGrid.zip

Session on MEF at Seattle Code Camp this Sunday

I am giving a session on the Managed Extensibility Framework (MEF) this Sunday at Seattle Code Camp.  I will be demoing how to build an extensible application using MEF.  It will be fun (I hope), heavily code-focused, and will cover most of the core features of MEF.

MEF was featured in the PDC keynote, and for a quick introduction to MEF, you can watch that part of the keynote here.

Posted by dsplaisted | 0 Comments
 
Page view tracker