I have IMessageSender interface.
using System.ComponentModel.Composition;
public interface IMessageSender
{
void Send(string message);
}
And I have two plugins that implements this interface. This is plugin.cs.
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System;
[Export(typeof(IMessageSender))]
public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine(message);
}
}
and this is plugin2.cs
[Export(typeof(IMessageSender))]
public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine(message + "!!!!");
}
}
And I have this code to run those plugins with MEF.
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System.Collections.Generic;
using System;
public class Program
{
[ImportMany]
public IEnumerable<IMessageSender> MessageSender { get; set; }
public static void Main(string[] args)
{
Program p = new Program();
p.Run();
foreach (var message in p.MessageSender) {
message.Send("hello, world");
}
}
public void Run()
{
Compose();
}
private void Compose()
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(@"./"));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
}
After compilation, I get what I want.
> mono program.exe
hello, world
hello, world!!!!
My question is how can I selectively run out of many plugins. This example just gets all the available plugins to run all of them, but what should I do when I just want to run first plugin or second plugin?
For example, can I run only plugin2.dll as follows?
public static void Main(string[] args)
{
Program p = new Program();
p.Run();
var message = messageSender.GetPlugin("plugin"); // ???
message.Send("hello, world");
}
Based on this site, and Matthew Abbott's answer. I could come up with this working code.
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System;
public interface IMessageSender
{
void Send(string message);
}
public interface IMessageSenderMetadata
{
string Name {get; }
string Version {get; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MessageMetadataAttribute : ExportAttribute, IMessageSenderMetadata
{
public MessageMetadataAttribute( string name, string version)
: base(typeof(IMessageSender))
{
Name = name;
Version = version;
}
public string Name { get; set; }
public string Version { get; set; }
}
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System;
[MessageMetadataAttribute("EmailSender1", "1.0.0.0")]
public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine(message + "????");
}
}
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System.Collections.Generic;
using System;
using System.Linq;
public class Program
{
[ImportMany(typeof(IMessageSender), AllowRecomposition = true)]
public IEnumerable<Lazy<IMessageSender, IMessageSenderMetadata>> Senders { get; set; }
public static void Main(string[] args)
{
Program p = new Program();
p.Run();
var sender1 = p.GetMessageSender("EmailSender1","1.0.0.0");
sender1.Send("hello, world");
sender1 = p.GetMessageSender("EmailSender2","1.0.0.0");
sender1.Send("hello, world");
}
public void Run()
{
Compose();
}
public IMessageSender GetMessageSender(string name, string version)
{
return Senders
.Where(l => l.Metadata.Name.Equals(name) && l.Metadata.Version.Equals(version))
.Select(l => l.Value)
.FirstOrDefault();
}
private void Compose()
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(@"./"));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
}
MEF supports the exporting of custom metadata to accompany your exported types. What you need to do, is first define an interface that MEF will use to create a proxy object containing your metadata. In your example, you'll likely need a unique name for each export, so we could define:
public interface INameMetadata
{
string Name { get; }
}
What you would then need to do, is make sure you assign that metadata for each of your exports that require it:
[Export(typeof(IMessageSender)), ExportMetadata("Name", "EmailSender1")]
public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine(message);
}
}
What MEF will do, is generate a project an implementation of your interface, INameMetadata
using the value stored in the ExportMetadata("Name", "EmailSender1")
atrribute.
After you've done that, you can do a little filtering, so redefine your [Import]
to something like:
[ImportMany]
public IEnumerable<Lazy<IMessageSender, INameMetadata>> Senders { get; set; }
What MEF will create is an enumerable of Lazy<T, TMetadata>
instances which support deferred instantiation of your instance type. We can query as:
public IMessageSender GetMessageSender(string name)
{
return Senders
.Where(l => l.Metadata.Name.Equals(name))
.Select(l => l.Value)
.FirstOrDefault();
}
Running this with an argument of "EmailSender1"
for the name
parameter will result in our instance of EmailSender
being returned. The important thing to note is how we've selected a specific instance to use, based on querying the metadata associated with the type.
You can go one further, and you could amalgamate the Export
and ExportMetadata
attributes into a single attribute, such like:
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false), MetadataAttribute]
public class ExportMessageSenderAttribute : ExportAttribute, INameMetadata
{
public ExportMessageSenderAttribute(string name)
: base(typeof(IMessageSender))
{
Name = name;
}
public string Name { get; private set; }
}
This allows us to use a single attribute to export a type, whilst still providing additional metadata:
[ExportMessageSender("EmailSender2")]
public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine(message);
}
}
Obviously querying this way presents you with a design decision. Using Lazy<T, TMetadata>
instances means that you'll be able to defer instantiation of the instance, but that does mean that only one instance can be created per lazy. The Silverlight variant of the MEF framework also supports the ExportFactory<T, TMetadata>
type, which allows you to spin up new instances of T
each time, whilist still providing you with the rich metadata mechanism.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With