Поделиться через


Overriding MEF Metadata

The Managed Extensibility Framework (MEF) is designed to allow open-ended extensibility.  It is easy to define a contract and load extensions which satisfy the contract.  This is accomplished with a collection import, which can look like this:

 [ImportMany]
public IPlugin[] Plugins { get; set; }

Extensions may need to be ordered or prioritized

Often, the importer will need a way to sort or prioritize the imported extensions.  If the extensions are displayed in a menu, the importer may want to control what order they appear in.  Or there may be multiple extensions which can be used, and the importer needs to choose the “best” one which can handle a given item.  For example, if extensions are used to provide UI controls for editing fields, there may be a generic editor which can edit all field types but only uses a simple textbox, and a specialized editor which only works on enums but provides a dropdown box for editing.  Both of these extensions could be used to edit an enum, but it is better to use the more specialized one.

Defining MEF metadata on an export

One way to represent the priority of an extension is with MEF metadata.  The exporter can add metadata using the ExportMetadataAttribute like this:

 [Export(typeof(IPlugin))]
[ExportMetadata("Name", "PluginA")]
[ExportMetadata("Priority", 1)]
public class PluginA : IPlugin { /* ... */ }

A custom export attribute can make this less verbose and add more compile-time checking.  With a custom export attribute, the export would look like this:

 [ExportPlugin("PluginA", Priority=1)]
public class PluginA : IPlugin

 

For details on how to create a custom export attribute, see the “Using a Custom Export Attribute” section of this page.

Using MEF metadata on the import side

To get access to MEF metadata, an importer can use an interface with properties corresponding to the metadata keys it wishes to access.  To access metadata for the name and priority, the following interface could be used:

 public interface IPluginMetadata
{
    string Name { get; }
    [DefaultValue(0)]
    int Priority { get; }
}

To use the interface, an importer can import into a collection of Lazy<T, TMetadata>, like this:

 [ImportMany]
public Lazy<IPlugin, IPluginMetadata>[] Plugins { get; set; }

Then plugins could be ordered by priority (highest to lowest) with the following LINQ expression:

 Plugins.OrderByDescending(lazy => lazy.Metadata.Priority);

Overriding metadata

Using MEF metadata for the priority puts the responsibility of defining the priority on the extension author.  An advantage of this is that when extensions are added to the system, they can “just work” without having to configure the priority.  On the other hand, the metadata can’t be modified without recompiling an extension.  Extensions from different sources will likely have been developed without any knowledge of each other, so the priority they provide may not be what is desired when they are used together.

So it would be useful to be able to override the metadata that is supplied with an extension.  I’ve prototyped a catalog that allows you to do this.  You create it as a wrapper around another MEF catalog, and then you tell it what metadata should be overridden before creating your container.  The following code creates a catalog and then sets the priority of exports from the PluginA class to 5:

 var catalog = new AttributedOverrideCatalog(new TypeCatalog(typeof(PluginA)));
catalog.SetMetadata("Priority", 5, typeof(PluginA));

How it works

The MetadataOverrideCatalog takes the part definitions from the wrapped catalog, and creates wrapper part definitions which return the overridden metadata but delegate all other functionality to the wrapped  part definition.  For an overview of what part definitions are, see my post on the MEF Primitives.

The MetadataOverrideCatalog is an abstract class with one abstract method: GetExportMetadata, which takes a part and an export as parameters and returns the metadata that should be used for the export (or null if the default metadata should be used).  This lets derived catalogs define the specifics of how metadata should be overridden.  The AttributedOverrideCatalog provides an implementation which lets you override metadata by passing in the type that the export is defined on.

This is just a sample—in general the hosting code won’t have a reference to the extension type, so you might want to write a catalog which determines what metadata to override based on a configuration file.

Download the source code here.

MetadataOverride.zip

Comments

  • Anonymous
    April 03, 2010
    Interesting. At first it seemed to me that you need to know the exact class type anyway in order to override its metadata, so why not just inherit from it or wrap it? But then I realized that my approach wouldn't work well with an ImportMany: you'd get both the original and the customized part unless you carefully removed the original part with a filtering catalog.

  • Anonymous
    April 05, 2010
    > At first it seemed to me that you need to know the exact class type anyway in order to override its metadata, so why not just inherit from it or wrap it? The idea I was trying to get across in the last paragraph is that you could write your own catalog which derives from MetadataOverrideCatalog which wouldn't need a static reference to a type to override its metadata.  It could be based on the types full name, or the existing export metadata, or something else, and the exports to override could be defined in a configuration file.  Then you would be able to change the metadata without recompiling any extensions or your own code.