Set med mine øjne, så er Managed Extensibility Framework (MEF) en af de rigtig interessante ting der kommer med .NET 4.0. Sagt lidt firkantet, så er MEF beregnet til på nem vis at lave understøttelse for plugins. Guiden på Codeplex er i rimelig stand efter releasen af Preview 4.
Pointen med en plugin model er, at man istedet for at have en applikaiton der er statisk kompileret, så har man en applikation der er dynamisk komponeret. Det betyder, at der på runtime loades plugins ind, som så tilføjer funktionallitet til applikationen. På denne vis, opnås en meget løst koblet applikation.
Kort sagt, så fungerer MEF ved at du kan dekorere metoder, klasser, properties og variable med Import og Export attributter. En Export attribut betyder at man stiller en klasse, metode, property eller variabel tilrådighed og en Import betyder at man konsumerer en Export. I nedenstående har jeg lavet lavet en producer/consumer applikation. Pointen er, at jeg har 2 produceres (men potentielt set mange) og en consumer, som skal kunne consume output fra producerne.
public interface IProducer
{
string Produce();
}
public interface IConsumer
{
IEnumerable<string> Consume();
}
[Export(typeof(IProducer))]
public class Producer : IProducer
{
public string Produce()
{
return "Hello World";
}
}
[Export(typeof(IProducer))]
public class SuperProducer : IProducer
{
public string Produce()
{
return "Hello Universe";
}
}
[Export(typeof(IConsumer))]
public class Consumer : IConsumer
{
[Import(typeof(IProducer))]
IEnumerable<IProducer> producers;
public IProducer producer { get; set; }
public IEnumerable<string> Consume()
{
foreach (var producer in producers)
{
yield return producer.Produce();
}
}
}
I ovenstående implementerer de to produceres interfacet IProducer. Begge producerklasser er dekoreret med [Export(typeof(IProducer))], hvilket vil sige, at de begge stiller klasserne tilrådighed eksponeret gennem IProducer interfacet. Man kan også bruge [Export("MinExport")] og tilsvarende import istedet for at bruge et interface eller en klasse som parameter (jeg foretrækker brugen af interfaces). Min consumerklasse implementerer IConsumer og er dekoreret med [Export(typeof(IConsumer))]. Desuden har Consumer klassen også en attribut der er dekoreret med [Import(typeof(IProducer))], hvilket er den mest interessante attribut i ovenstående, fordi den “binder” alle Exports til sig der matcher typeof(IProducer). Variablen “IEnumerable<IProducer> producers” vil altså i den “dynamiske komponering” blive instantieret med alle de exports der matcher importen (Producer og SuperProducer).
Men lad os kigge på den dynamiske komponering. Nedenfor anvender jeg ovenstående consumer.
class Program
{
[Import(typeof(IConsumer))]
IConsumer cons;
public void Run()
{
foreach (var item in cons.Consume())
{
Console.WriteLine(item);
}
}
public Program()
{
var catalog = new AggregateCatalog(new[] {
new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()) });
var container = new CompositionContainer(catalog);
var batch = new CompositionBatch();
batch.AddPart(this);
container.Compose(batch);
}
static void Main(string[] args)
{
Program p = new Program();
p.Run();
Console.ReadKey();
}
}
Hele magien sker i constructoren Program(). Det er her den dynamiske komponering sker. I dette eksempel er komponeringen ret simpel. Jeg kigger kun på “Executing Assembly”, men jeg kunne have kigget på andre assemblies, hvis jeg have haft imports eller exports i dem som jeg ville drage nytte af. I sidste linie af constructoren kalder jeg compose. Det er compose der matcher alle imports og exports, som er indeholdt i mit catalog.
Når jeg har kaldt compose, så er alt instantieret. Variablen “IConsumer cons” nu bundet til et Consumer objekt. Mit Consumer objekt har desuden fået instantieret “IEnumerable<IProducer> producers” og jeg kan nu gennem min cons enumerere mine consumeres og kalde consume på dem, hvilket jeg gør i Run().
Ret elegant. Meget nemt. Super letvægtigt. Det er nu helt frit for alle at lave Produceres til min applikation, bare de implementerer IProducer og er dekoreret med korrekte attributter. Blandt venner, så fungerer dette fint, så længe der ikke er nogen der laver en EvilProducer : IProducer.
Vil man stille en plugin arkitektur til rådighed for eksterne, så ville jeg også kigge på CLR-Addins, som blandt andet stiller plug-in/add-in sandboxing og security tilrådighed. Der er er par gode artikler om CLR-addins her (1) (2).