Getting only necessary plugins with MEF in .NET - c#

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");
}
SOLVED
Based on this site, and Matthew Abbott's answer. I could come up with this working code.
interface code (interface.cs)
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; }
}
Plugin code (Plugin.cs ...)
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 + "????");
}
}
Program.cs
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.

Related

MEF2 Lightweight System.Composition with Metadata

The complete lack of examples for how to use lightweight MEF2, System.Composition, makes this tricky. I am only using System.Composition (not System.ComponentModel.Composition).
I want to import parts that have metadata. I'm using attributed code. Unfortunately when I try to get the exports I get a big fat null.
The MetadataAttribute:
namespace Test.ConfigurationReaders
{
using System;
using System.Composition;
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ExportReaderAttribute : ExportAttribute
{
public string Reader { get; set; }
}
}
Export:
namespace Test.ConfigurationReaders
{
using System.Collections.Generic;
using System.Configuration;
[ExportReader(Reader = "CONFIG")]
public class ConfigReader : IConfigurationReader
{
public IEnumerable<Feature> ReadAll()
{
return new List<Feature>();
}
}
}
ImportMany and filter on metadata:
namespace Test.Core
{
using System;
using System.Collections.Generic;
using System.Composition;
using System.Composition.Hosting;
using System.Configuration;
using System.Linq;
using System.Reflection;
using ConfigurationReaders;
public sealed class Test
{
static Test()
{
new Test();
}
private Test()
{
SetImports();
Reader = SetReader();
}
[ImportMany]
private static IEnumerable<ExportFactory<IConfigurationReader, ExportReaderAttribute>> Readers { get; set; }
public static IConfigurationReader Reader { get; private set; }
private static IConfigurationReader SetReader()
{
// set the configuation reader based on an AppSetting.
var source =
ConfigurationManager.AppSettings["Source"]
?? "CONFIG";
var readers = Readers.ToList();
var reader =
Readers.ToList()
.Find(
f =>
f.Metadata.Reader.Equals(
source,
StringComparison.OrdinalIgnoreCase))
.CreateExport()
.Value;
return reader;
}
private void SetImports()
{
var configuration =
new ContainerConfiguration().WithAssemblies(
new List<Assembly> {
typeof(IConfigurationReader).Assembly });
var container = configuration.CreateContainer();
container.SatisfyImports(this);
Readers = container.GetExports<ExportFactory<IConfigurationReader, ExportReaderAttribute>>();
}
}
}
Readers is, unfortunately, null. This is example code here but I can see parts that don't have metadata in my actual code so that at least is working.
What should I do to populate Readers?
I'm trying to target .NET Standard 2.0 and use it from .NET Core.
The solution is to change the attributes for the exported class:
[Export(typeof(IConfigurationReader))]
[ExportMetadata("Reader", "CONFIG")]
public class ConfigReader : IConfigurationReader
{
}

MEF plugin calling another plugin with same interface

I am trying to make a (my first MEF) system in which plugins can be recursive, i.e. my main system calls a MEF plugin with a standard interface, which on its own can then call another (or several) plugin(s), and so on.
When testing though, my plugin does not call the underlying plugin, but starts processing itself (creating a loop).
Any idea how I can prevent this?
Interface:
public interface IConnector
{
XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys);
}
My main plugin inherits the interface, and defines the import for the next (The subplugin has the same definition):
[Export(typeof(IConnector))]
public class Connector : IConnector
{
[Import(typeof(IConnector))]
private IConnector connector;
....
The called plugin is initiated (in the Run method of the main plugin):
public XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys)
{
string calledConnector = Path.Combine(AssemblyDirectory, "subplugin.dll");
AssemblyCatalog assembyCatalog = new AssemblyCatalog(Assembly.LoadFrom(calledConnector));
CompositionContainer container = new CompositionContainer(assembyCatalog);
container.ComposeParts(this);
....
The container should now contain just one plugin, the subplugin.dll.
I call the method 'Run' which is in the interface to invoke the subplugin method:
XDocument something = connector.Run(serviceCredentials, connectorids, connectorkeys);
But, instead of running the subplugin code, the 'Run' method in my main plugin activates, which keeps activating itself.
When I remove the [Export(typeof(iConnector)] in the main plugin, the subplugin is activated, but I want my main plugin to be able to be called in the same manner.
Being new to MEF I am stuck as to how to solve this. Any help would be much appreciated!
You should use Contracts and specify your intent, otherwise MEF will go into an infinite loop or pick the Connector as it exposes IConnector itself.
Some more info from MSDN.
For example
[Export("Container", typeof(IConnector))]
public class Connector : IConnector
{
[Import("Component", typeof(IConnector))]
private IConnector connector;
....
UPDATE
So after giving it some thought, here is an example of metadata based approach, and one that also limits the number of expensive catalog operations.
The IConnector
using System.Xml.Linq;
namespace Common
{
public interface IConnector
{
XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys);
void Identify();
}
}
The metadata attribute ConnectorMetadata
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
namespace Common
{
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class ConnectorMetadata : ExportAttribute
{
public string Name { get; private set; }
public ConnectorMetadata(string name):base(typeof(IConnector))
{
Name = name;
}
public ConnectorMetadata(IDictionary<string, object> metaData) : base(typeof (IConnector))
{
Name = Convert.ToString(metaData["Name"]);
}
}
}
The lazy singleton for PluginsCatalog
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using Common;
namespace Common
{
public class PluginsCatalog
{
[ImportMany]
public Lazy<IConnector, ConnectorMetadata>[] Connectors;
private static readonly Lazy<PluginsCatalog> LazyInstance = new Lazy<PluginsCatalog>(() => new PluginsCatalog());
private PluginsCatalog()
{
var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Directory.GetCurrentDirectory();
var directoryCatalog = new DirectoryCatalog(path, "*plugin.dll");
var aggregateCatalog = new AggregateCatalog(assemblyCatalog, directoryCatalog);
var container = new CompositionContainer(aggregateCatalog);
container.SatisfyImportsOnce(this);
}
public static PluginsCatalog Instance { get { return LazyInstance.Value; } }
public IConnector GetConnector(string name)
{
var match = Connectors.SingleOrDefault(s => s.Metadata.Name.Equals(name));
return match == null ? null : match.Value;
}
}
}
The "Primary" IConnector
using System;
using System.Xml.Linq;
using Common;
namespace Common
{
[ConnectorMetadata("Primary")]
public class Connector : IConnector
{
public XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys)
{
PluginsCatalog.Instance.GetConnector("Sub").Identify();
return default(XDocument);
}
public void Identify()
{
Console.WriteLine(GetType().FullName);
}
}
}
The "Sub" IConnector
using System;
using System.Xml.Linq;
using Common;
namespace SubPlugin
{
[ConnectorMetadata("Sub")]
public class SubConnector:IConnector
{
public XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys)
{
return default(XDocument);
}
public void Identify()
{
Console.WriteLine(GetType().FullName);
}
}
}
and finally the program itself:
namespace SOMEF
{
class Program
{
static void Main(string[] args)
{
var connector = PluginsCatalog.Instance.GetConnector("Primary");
connector.Identify();
connector.Run(null, null, null);
}
}
}
Which prints:
SOMEF.Connector
SubPlugin.SubConnector
Hope this helps ... :)
You might want to read this https://msdn.microsoft.com/en-us/library/ee155691(v=vs.110).aspx
With exmplanation how named exports are used
public class MyClass
{
[Import("MajorRevision")]
public int MajorRevision { get; set; }
}
public class MyExportClass
{
[Export("MajorRevision")] //This one will match.
public int MajorRevision = 4;
[Export("MinorRevision")]
public int MinorRevision = 16;
}

Static method of class A is not called from the static constructor of class B

I have the following classes
public class A
{
protected static Dictionary<string,Func<BaseClass>> dict = new Dictionary<string,Func<BaseClass>>();
public static void AddGenerator(string type,Func<BaseClass> fncCreateObject)
{
dict.Add(type,fncCreateObject);
}
}
class B : BaseClass
{
static B()
{
A.AddGenerator("b",CreateObject);
}
protected B()
{}
pulic static B CreateObject()
{
return new B();
}
}
NOTE: The above code is simply an example but very closely relates to the what I'm trying to achieve.
Many people would advice using an IoC container such as NInject or Unity but my main reason for this post if to figure out why the above code does not execute as it is expected to.
So, in the above code, I'm expecting class B's static constructor to call on the static method of class A and an entry should be available in the dictionary for the rest of the application life cycle.
However, when I run the code and debug, I found that the dictionary is empty.
Why is the code invoked from class B's static constructor not executing?
From the documentation:
A static constructor is called automatically to initialize the class before the first instance is created or any static members are referenced.
Clearly, at the point in your code where you inspect the dictionary, no instance has yet been created, and no static members have been referenced.
Not exactly a 1:1 translation, of your sample into MEF, but it should give you a good idea what MEF is capable of:
using System;
using System.Collections.Generic;
namespace ConsoleApplication4
{
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var directoryCatalog = new DirectoryCatalog(".");
var compositeCatalog = new AggregateCatalog(assemblyCatalog, directoryCatalog);
var container = new CompositionContainer(compositeCatalog);
var a = A.Instance;
container.SatisfyImportsOnce(a);
a.PrintCatalog();
}
}
public sealed class A
{
private static readonly A instance = new A();
static A() { }
private A() { }
public static A Instance { get { return instance; } }
[ImportMany]
private List<IBType> BTypes;
public void PrintCatalog()
{
foreach (var bType in BTypes)
{
Console.WriteLine(bType.GetType());
}
}
}
[Export(typeof(IBType))]
class B:IBType
{
static B()
{
}
protected B()
{}
public void DoSomething() { }
}
[Export(typeof(IBType))]
class B2:IBType
{
static B2()
{
}
protected B2()
{}
public void DoSomething() { }
}
interface IBType
{
void DoSomething();
}
}
I've also included the safest implementation of a Singleton pattern known to me. MEF will allow you to source many implementations of the same interface which are resolved dynamically at runtime. I used it also with metadata attributes, like version and name.
But if you need it to work with a base abstract class, check out this article.
The same code as above, but with metadata attributes use sample:
using System;
using System.Collections.Generic;
namespace ConsoleApplication4
{
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var directoryCatalog = new DirectoryCatalog(".");
var compositeCatalog = new AggregateCatalog(assemblyCatalog, directoryCatalog);
var container = new CompositionContainer(compositeCatalog);
var a = A.Instance;
container.SatisfyImportsOnce(a);
a.PrintCatalog();
a.BTypes.Single(s=>s.Metadata.Name.Equals("Second")).Value.DoSomething();
}
}
public sealed class A
{
private static readonly A instance = new A();
static A() { }
private A() { }
public static A Instance { get { return instance; } }
[ImportMany]
public List<Lazy<IBType,IBTypeMetadata>> BTypes;
public void PrintCatalog()
{
foreach (var bType in BTypes)
{
Console.WriteLine(bType.Value.GetType());
}
}
}
[Export(typeof(IBType))]
[BTypeMetadata("First")]
class B:IBType
{
static B()
{
}
protected B()
{}
public void DoSomething() { }
}
[Export(typeof(IBType))]
[BTypeMetadata("Second")]
class B2 : IBType
{
static B2()
{
}
protected B2()
{}
public void DoSomething()
{
Console.WriteLine("Hello from Second");
}
}
public interface IBType
{
void DoSomething();
}
public interface IBTypeMetadata
{
string Name { get; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class BTypeMetadataAttribute : ExportAttribute
{
public string Name { get; set; }
public BTypeMetadataAttribute(string name)
: base(typeof(IBTypeMetadata)) { Name = name; }
}
}
IMHO, MEF might help you as long as your plan is to call some public methods from a particular instance of any of the B-types. In your sample, you simply create new instances of a B-type, and I think there is more to it than what your sample shows.
MEF will create catalogs for you from your currently loaded assembly, as well as any number of assemblies from any number of directories. You can even have it dynamically re-composable, meaning, at runtime, you could potentially retrieve a DLL from a server, and have it added to your catalog without shutting down the application.
MEF is also hierarchical, so your B-types can have their own "catalogs". And to wire it all up, all you have to do is to call SatifyImportsOnce passing an instance of class A.

Unity.AutoRegistration ?? Convention Based Registration Unity

My code is as follows
IUnityContainer container = new UnityContainer();
container
.ConfigureAutoRegistration()
.LoadAssemblyFrom(typeof(Test).Assembly.Location)
.LoadAssemblyFrom(typeof(ITest).Assembly.Location)
.ApplyAutoRegistration();
This is my first question.
I'm not sure whether I have used the LoadAssemblyFrom method correctly here:
ITest test = container.Resolve<ITest>();
When I try to compile I get the exception "ResolutionFailedException".
What am I doing wrong?
Thanks for your time in advance.
It appears that what you are looking for is this:
container.ConfigureAutoRegistration()
.LoadAssemblyFrom(typeof(ITest).Assembly.Location)
.LoadAssemblyFrom(typeof(Test).Assembly.Location)
.Include(If.ImplementsITypeName, Then.Register())
.ApplyAutoRegistration();
This will tell Unity.AutoRegistration to register all types where there is an interface with the same name, prefixed with I.
Here is a complete working console example showing how to set Unity up for registration by convention, then transfer control into the dependency injection world. You will have to add the Unity NuGet package for this to work.
Tested with Unity v3.5 and VS 2012.
#region
using System;
using Microsoft.Practices.Unity;
#endregion
namespace Demo___Unity
{
internal class Program
{
private static void Main(string[] args)
{
using (var container = new UnityContainer())
{
// Manual method.
//container.RegisterType<IEntryPoint, EntryPoint>();
//container.RegisterType<IInjected, Injected>();
// Set up registration by convention.
// http://blogs.msdn.com/b/agile/archive/2013/03/12/unity-configuration-registration-by-convention.aspx
container.RegisterTypes(
AllClasses.FromAssembliesInBasePath(),
WithMappings.FromMatchingInterface,
WithName.Default,
WithLifetime.ContainerControlled);
var controller = container.Resolve<IEntryPoint>();
controller.Main();
}
}
}
public interface IEntryPoint
{
string Name { get; set; }
void Main();
}
public class EntryPoint : IEntryPoint
{
private readonly IInjected Injected;
public EntryPoint(IInjected injected)
{
Injected = injected;
}
public void Main()
{
Console.Write("Hello, world!\n");
Injected.SubMain();
Injected2.SubMain();
Console.Write("[any key to continue]");
Console.ReadKey();
}
// Demonstrates property injection.
[Dependency]
public IInjected Injected2 { get; set; }
public string Name { get; set; }
}
public interface IInjected
{
void SubMain();
}
public class Injected : IInjected
{
public void SubMain()
{
Console.Write("Hello, sub world!\n");
}
public string Name { get; set; }
}
}

MEF attribute-free approach: lazy initialization by condition

My previous post contains attempt use attribute-free (convention based) approach to configure MEF: MEF 2: import many.
But it contains export metadata attribute usage in the class PluginMetadataAttribute needed for lazy initialization plugin by condition (specific name, version).
How to get rid of ExportAttribute dependency?
I found three solution.
Solution 1 (using class constant fields, poor solution):
public class Plugin1 : IPlugin
{
public const string Name = "Plugin1";
public const string Version = "1.0.0.0";
public void Run()
{
Console.WriteLine("Plugin1 runed");
}
}
// ...
var builder = new RegistrationBuilder();
builder
.ForTypesDerivedFrom<IPlugin>()
.Export<IPlugin>(exportBuilder => {
exportBuilder.AddMetadata("Name", t => t.GetField("Name").GetRawConstantValue());
exportBuilder.AddMetadata("Version", t => t.GetField("Version").GetRawConstantValue());
});
Solution 2 (using class properties, poor solution):
public interface IPluginMetadata
{
string Name { get; }
string Version { get; }
}
public interface IPlugin : IPluginMetadata
{
void Run();
}
public class Plugin1 : IPlugin
{
public string Name { get { return "Plugin 1"; } }
public string Version { get { return "1.0.0.0"; } }
public void Run()
{
Console.WriteLine("Plugin1 runed");
}
}
And get properties values by method described this: https://stackoverflow.com/a/11162876/1986524
Solution 3 (using attributes, better but not all happy):
using System;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;
using System.Reflection;
namespace MEF2
{
public interface IPluginMetadata
{
string Name { get; }
string Version { get; }
}
public interface IPlugin
{
void Run();
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginMetadataAttribute : Attribute, IPluginMetadata
{
public string Name { get; set; }
public string Version { get; set; }
public PluginMetadataAttribute(string name, string version)
{
Name = name;
Version = version;
}
}
[PluginMetadata("Plugin1", "1.0.0.0")]
public class Plugin1 : IPlugin
{
public void Run()
{
Console.WriteLine("Plugin1 runed");
}
}
[PluginMetadata("Plugin2", "2.0.0.0")]
public class Plugin2 : IPlugin
{
public void Run()
{
Console.WriteLine("Plugin2 runed");
}
}
class Program
{
static void Main(string[] args)
{
var builder = new RegistrationBuilder();
builder
.ForTypesDerivedFrom<IPlugin>()
.Export<IPlugin>(exportBuilder => {
exportBuilder.AddMetadata("Name", t => t.GetCustomAttribute<PluginMetadataAttribute>().Name);
exportBuilder.AddMetadata("Version", t => t.GetCustomAttribute<PluginMetadataAttribute>().Version);
});
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly(), builder);
using (var container = new CompositionContainer(catalog, CompositionOptions.DisableSilentRejection)) {
var plugins = container.GetExports<IPlugin, IPluginMetadata>();
foreach (var plugin in plugins) {
Console.WriteLine("{0}, {1}", plugin.Metadata.Name, plugin.Metadata.Version);
plugin.Value.Run();
}
}
}
}
}
Solution 3 contains problem in this code:
.Export<IPlugin>(exportBuilder => {
exportBuilder.AddMetadata("Name", t => t.GetCustomAttribute<PluginMetadataAttribute>().Name);
exportBuilder.AddMetadata("Version", t => t.GetCustomAttribute<PluginMetadataAttribute>().Version);
})
Problems:
Can't cancel add metadata in case of missing metadata
Duplicate code t.GetCustomAttribute<PluginMetadataAttribute>()
Export<> don't provided filter
If anyone knows of other solutions please write.
The An Attribute-Free Approach to Configuring MEF article that you reference in your other question includes an example on how to add metadata without using an attribute.
The example shows a use of the PartBuilder.ExportProperties overload that takes an Action<PropertyInfo, ExportBuilder> as a parameter and use one of the ExportBuilder.AddMetadata overloads to add metadata for the specific export.
This is not the only way to add metadata. All export methods of PartBuilder have an overload that take an Action<> (or an Action<,>) with an ExportBuilder param. You can use these overloads and add your metadata in a similar way.

Categories