In my earlier post on using MEF's Aggregate Catalog, I said, "It is also possible to attach metadata to each import, and order based on the metadata, but we'll save metadata for another post." We have now arrived at that other post. I'll update the example in the earlier post to select the desired implementation of an interface using metadata.
MEF supports an attribute, ExportMetadata, that allows you to associate string/object pair with your exported type. You can have many ExportMetadata attributes; the strings serve as keys in a dictionary, and the objects are the values. In our case, we only need one string/value pair. We're going to use it to attach a preference level to each implementation of our interface. Our program is going to select the implementation with the hightest preference level.
You can read the earlier post for an overview of the project, but to cut to the chase here are the two implementations of our IStringWriter interface. Notice the ExportMetadata attribute on each.
The Default IStringWriter
using System;
using System.ComponentModel.Composition;
using MefAggregate.Types;
namespace MefAggregate.Main
{
[Export(typeof(IStringWriter))]
// This default implementation has PreferenceLevel zero.
[ExportMetadata("PreferenceLevel", 0)]
class DefaultWriter : IStringWriter
{
public void Write(string str)
{
Console.WriteLine("DefaultWriter: {0}", str);
}
}
}
The Override IStringWriter
using System;
using System.ComponentModel.Composition;
using MefAggregate.Types;
namespace MefAggregate.Override
{
[Export(typeof(IStringWriter))]
// We override with preference level that
// is higher than the one in the default implementation.
[ExportMetadata("PreferenceLevel",100)]
public class OverrideWriter : IStringWriter
{
public void Write(string str)
{
Console.WriteLine("OverrideWriter: {0}", str);
}
}
}
Using the Metadata
Now we can use the metadata as simply as this:
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using MefAggregate.Types;
namespace MefAggregate.Main
{
class Writer : IStringWriter
{
[ImportMany]
IEnumerable<Lazy<IStringWriter, IDictionary<string, object>>> _writers = null;
IStringWriter SelectedWriter
{
get
{
return _writers
.OrderBy(w => (int)w.Metadata["PreferenceLevel"])
.Last()
.Value;
}
}
public void Write(string str)
{
SelectedWriter.Write(str);
}
}
}
We use ImportMany just as we did before, but this time instead of importing an IEnumerable of IStringWriters, we have an IEnumerable of Lazy references. The first type parameter to Lazy is the interface (IStringWriter), and the second is the type of metadata we want. We're saying we want an IDictionary of string/object pairs. With the Lazy class, we can inspect the metadata without even loading the assemblies that have the exports! Plus, nothing at all happens until our SelectedWriter property is used.
In that property, we use LINQ to iterate over the IStringWriters and select the one with the highest PreferenceLevel. That's all there is to it!
Using this technique, you can build an application with default implementations of key interfaces, which you can override at run-time.