The Managed Extensibility Framework (MEF) is the .NET framework for building applications that are extensible with external plugins after being deployed. I already hear you thinking: "D'oh! Not yet another Reflection slash Inversion of Control slash Composition framework ?" And yes, that's what it is. But here's the good news: it's easy to use, it fits in a single assembly, and it comes with good credentials and a proven track record. The assembly -System.ComponentModel.Composition- ships with the .NET framework 4.0 as well as with Silverlight 4.0. You can also download it from CodePlex together with source code and some flashy (but unfortunately also quite complex) examples.
Terminology
The MEF objectmodel is built around the following concepts:
- Part - a component that delivers or consumes services,
- Export - a service (class or interface) provided by a part,
- Import - a service (class or interface) consumed by a part,
- Contract - a formal description of imports and exports,
- Catalog - a means to publish and discover parts, and
- Composition - building the object graph by hooking up exporters to importers.
The sample
I built a small console app to demonstrate the core features. Here's how it looks like:
It looks dull on the outside, doesn't it? I agree, but the beauty is within. This application actually composes its object graph dynamically -at run time- from assemblies that it discovers in a folder. Functionally, the console application hooks together electric devices (laptop, television, XBox) and electric power suppliers (nuclear power plants, wind parks, brown coal power stations). Technically, the solution looks like this. Except for the Contract assembly, there are NO references between the projects:
Defining a Contract
I started with creating an assembly -called Contracts- that describes the services. It's nothing more than a class library with some interface definitions. It will be referenced by all application components - host and parts. Here's the definition of the interfaces:
namespace Contracts
{
public interface IPowerSupplier
{
string EnergySource { get; }
}
}
namespace Contracts
{
public interface IPowerConsumer
{
string Start();
IPowerSupplier PowerSupplier { get; set; }
}
}
Export: Exposing a Service
The next step is to build some classes that implement these interfaces, and expose their services to MEF. A class indicates exposed services by an [Export] attribute. This is my representation of the well-known Springfield Nuclear Power Plant:
namespace Springfield
{
using System.ComponentModel.Composition; // MEF
using Contracts; // Power Supplier Contract
[Export(typeof(IPowerSupplier))]
public class NuclearPowerPlant : IPowerSupplier
{
public string EnergySource
{
get
{
return "split atoms";
}
}
}
}
Import: Requiring a Service
A class indicates the services that it requires from MEF through the [Import] attribute. The following is a representation of my laptop. It exports itself as an IPowerConsumer, but it expects an IPowerSupplier from MEF:
namespace My
{
using System.ComponentModel.Composition; // MEF
using Contracts;
[Export(typeof(IPowerConsumer))]
public class Laptop : IPowerConsumer
{
[Import]
public IPowerSupplier PowerSupplier { get; set; }
public string Start()
{
return string.Format(
"{0} {1} is writing blog entries thanks to the {2} from {3} {4}.",
this.GetType().Namespace,
this.GetType().Name,
this.PowerSupplier.EnergySource,
this.PowerSupplier.GetType().Namespace,
this.PowerSupplier.GetType().Name);
}
}
}
Hooking an importer to an exporter
Let's now focus on the host application that will do the composition. It contains a PowerPlug class that directly hooks a Power Consumer to a Power Supplier. Through the [Import] attribute in its implementation, my Laptop already indicates to MEF that it requires a Supplier. So the PowerPlug itself only needs to inform MEF that it requires a Consumer:
public class PowerPlug
{
[Import(typeof(IPowerConsumer))]
public IPowerConsumer consumer { get; set; }
}
PowerPlug's constructor looks up all assemblies in a specified directory, then loads the classes that are decorated with the mentioned attributes, and finally hooks them all up:
public PowerPlug()
{
var catalog = new DirectoryCatalog(@"../../Hooked");
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
All you need to do is instantiating the PowerPlug, and access the object graph:
PowerPlug plug = new PowerPlug();
Console.WriteLine(string.Format(" {0}\n", plug.consumer.Start()));
Please observe that consumer ànd supplier were discovered and connected.
Importing many Exports
MEF allows you to import whole collections of instances that expose the same service. So I created another power supplier, the Thorntonbank Wind Farm near the belgian coast:
namespace ThorntonBank
{
using System.ComponentModel.Composition; // MEF
using Contracts; // Power Supplier Contract
[Export(typeof(IPowerSupplier))]
public class WindFarm : IPowerSupplier
{
public string EnergySource
{
get
{
return "wind";
}
}
}
}
In the host application I created a DistributionOperator class that represents a power grid owner, like Elia. Instances of the class require a list of Suppliers from MEF. DistributionOperator exposes that need through the [ImportMany] attribute:
[ImportMany]
public IEnumerable<IPowerSupplier> Suppliers { get; set; }
Its constructor is similar to the one from PowerPlug: it looks up all classes in a directory (please note that MEF is equipped with other container types). At run time, just instantiate, and the objectmodel will be available automagically:
DistributionOperator elia = new DistributionOperator();
foreach (var supplier in elia.Suppliers)
{
Console.WriteLine(
" {0} {1} provides electricity from {2}.",
supplier.GetType().Namespace,
supplier.GetType().Name,
supplier.EnergySource);
}
Exposing Metadata
Parts can expose metadata in the form of Key-Value pairs to MEF through the [ExportMetadata] attribute. The ThorntonBank WindFarm could indicate to MEF that it's a very Kyoto-friendly power source by decorating itself as follows:
[ExportMetadata("EnergyType", "Renewable")]
[Export(typeof(IPowerSupplier))]
public class WindFarm : IPowerSupplier
{...}
This works, but in the strongly-typed world it just makes more sense to define some helper classes in the Contracts-assembly:
namespace Contracts
{
public enum EnergyTypes
{
Renewable,
Questionable,
GloballyWarming
}
public class PowerSupplierMetadata
{
public const string EnergyType = "EnergyType";
}
}
This results in a safer way to expose the same information, but also one that's easier to consume by other components:
[ExportMetadata(PowerSupplierMetadata.EnergyType, EnergyTypes.Renewable)]
[Export(typeof(IPowerSupplier))]
public class WindFarm : IPowerSupplier
{
// ...
}
Consuming Metadata
MEF components can access the metadata, otherwise the concept would make no sense. The MEF assembly contains the very useful (not MEF-specific) Lazy<T> class, together with (MEF-specific) Lazy<T, TMetadata>. The latter gives the framework access to the class' metadata without the need for instantiation. TMetadata exposes the keys in the metadata as read-only properties. So I needed to create yet another helper (the Contracts-assembly seemed a good place):
public interface IPowerSupplierMetadata
{
EnergyTypes EnergyType { get; }
}
The DistributionOperator class can now be extended with a list of Lazy<IPowerSupplier, IPowerSupplierMetadata>:
[ImportMany]
private IEnumerable<Lazy<IPowerSupplier, IPowerSupplierMetadata>> SuppliersWithMetadata { get; set; }
The instances of this list get a MetaData property holding the metadata (obviously), and a Value property that gives access to the PowerSupplier instance. So the DistributionOperator could expose a list of green power suppliers like this:
public IEnumerable<IPowerSupplier> KyotoFriendlySuppliers()
{
var query = from s in this.SuppliersWithMetadata
where s.Metadata.EnergyType.Equals(EnergyTypes.Renewable)
select s.Value;
return query;
}
Here's how the console app consumes the list:
foreach (var supplier in elia.KyotoFriendlySuppliers())
{
Console.WriteLine(
" {0} {1} provides green electricity from {2}.",
supplier.GetType().Namespace,
supplier.GetType().Name,
supplier.EnergySource);
}
What's next
This is as good as it gets - at least in 300 seconds. If you're still interested in MEF, then make sure you don't skip this excellent article in MSDN Magazine.
Source code
Here's the full source code. It is based on preview 9 of MEF from CodePlex, and ye olde Visual Studio.NET 2008: U2UConsult.MEF.Sample.zip (2,52 mb)
Enjoy!