I am trying to get example 2 of how to use policies in StructureMap working. I have created a small test-project (code below). Unfortunately for some reason there seem to be some dependency issues, since the .As<type> and .Each are both not working.
For the lines using As such as
user.Green.As<Database>().ConnectionString.ShouldBe("*green*");
I am getting the error:
'IDatabase' does not contain a definition for 'As' and no extension method 'As' accepting a first argument of type 'IDatabase' could be found (are you missing a using directive or assembly reference?). And for this line:
instance.Constructor.GetParameters()
.Where(x => x.ParameterType == typeof(IDatabase))
.Each(param => ...
'StringExtensions.Each(IEnumerable, Action)' is inaccessible due to its protection level.
I installed StructureMap 4.2 using NuGet. As you can see in the code below, I put in all the using-statements from StructureMap, that I could find, but I am still having the problem.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StructureMap;
using StructureMap.Pipeline;
using StructureMap.Pipeline.Lazy;
using StructureMap.Configuration;
using StructureMap.Building;
using StructureMap.Attributes;
using StructureMap.AutoMocking;
using StructureMap.Graph;
using StructureMap.Query;
using StructureMap.TypeRules;
using StructureMap.Util;
using StructureMap.Building.Interception;
using StructureMap.Configuration.DSL;
using StructureMap.Diagnostics.TreeView;
using StructureMap.Graph.Scanning;
namespace TestStructureMapPolicies
{
class Program
{
static void Main(string[] args)
{
var container = new Container(_ =>
{
_.For<IDatabase>().Add<Database>().Named("red")
.Ctor<string>("connectionString").Is("*red*");
_.For<IDatabase>().Add<Database>().Named("green")
.Ctor<string>("connectionString").Is("*green*");
_.Policies.Add<InjectDatabaseByName>();
});
// ImportantService should get the "red" database
container.GetInstance<ImportantService>()
.DB.As<Database>().ConnectionString.ShouldBe("*red*");
// BigService should get the "green" database
container.GetInstance<BigService>()
.DB.As<Database>().ConnectionString.ShouldBe("*green*");
// DoubleDatabaseUser gets both
var user = container.GetInstance<DoubleDatabaseUser>();
user.Green.As<Database>().ConnectionString.ShouldBe("*green*");
user.Red.As<Database>().ConnectionString.ShouldBe("*red*");
}
}
public interface IDatabase { }
public class Database : IDatabase
{
public string ConnectionString { get; set; }
public Database(string connectionString)
{
ConnectionString = connectionString;
}
public override string ToString()
{
return string.Format("ConnectionString: {0}", ConnectionString);
}
}
public class InjectDatabaseByName : ConfiguredInstancePolicy
{
protected override void apply(Type pluginType, IConfiguredInstance instance)
{
instance.Constructor.GetParameters()
.Where(x => x.ParameterType == typeof(IDatabase))
.Each(param =>
{
// Using ReferencedInstance here tells StructureMap
// to "use the IDatabase by this name"
var db = new ReferencedInstance(param.Name);
instance.Dependencies.AddForConstructorParameter(param, db);
});
}
}
public class BigService
{
public BigService(IDatabase green)
{
DB = green;
}
public IDatabase DB { get; set; }
}
public class ImportantService
{
public ImportantService(IDatabase red)
{
DB = red;
}
public IDatabase DB { get; set; }
}
public class DoubleDatabaseUser
{
public DoubleDatabaseUser(IDatabase red, IDatabase green)
{
Red = red;
Green = green;
}
// Watch out for potential conflicts between setters
// and ctor params. The easiest thing is to just make
// setters private
public IDatabase Green { get; private set; }
public IDatabase Red { get; private set; }
}
}
The As<T>() extension method I used in the tests isn't part of the BCL and that's why you're not finding it. If you're using Shouldly, you could effect the same thing with ShouldBeOfType<T>() or just cast it normally before making the comparisons. Same thing with the Each() extension.
Related
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
{
}
I'm using:
EF 6.2,
VisualStudio 2017,
nUnit 2.6.3.13283 (unit test),
Unity 5.8.5 (as IoC).
The problem appears when I want to test two different DbContexts in the same UnitTest.
First Context:
public class MsSqlConfiguration : System.Data.Entity.DbConfiguration
{
public MsSqlConfiguration()
{
this.SetDefaultConnectionFactory(new System.Data.Entity.Infrastructure.SqlConnectionFactory());
this.SetProviderServices("System.Data.SqlClient", System.Data.Entity.SqlServer.SqlProviderServices.Instance);
}
}
[DbConfigurationType(typeof(MsSqlConfiguration))]
public class SqlDbContext: DbContext
{
public SqlDbContext(string connectonString) : base(connectonString)
{}
public DbSet<SomeClass> SomeField { get; set; }
}
Second context:
public class SQLiteProviderInvariantName : IProviderInvariantName
{
public static readonly SQLiteProviderInvariantName Instance = new SQLiteProviderInvariantName();
private SQLiteProviderInvariantName() { }
public const string ProviderName = "System.Data.SQLite.EF6";
public string Name { get { return ProviderName; } }
}
class SQLiteDbDependencyResolver : IDbDependencyResolver
{
public object GetService(Type type, object key)
{
if (type == typeof(IProviderInvariantName)) return SQLiteProviderInvariantName.Instance;
if (type == typeof(DbProviderFactory)) return SQLiteProviderFactory.Instance;
return SQLiteProviderFactory.Instance.GetService(type);
}
public IEnumerable<object> GetServices(Type type, object key)
{
var service = GetService(type, key);
if (service != null) yield return service;
}
}
public class SQLiteConfiguration : System.Data.Entity.DbConfiguration
{
public SQLiteConfiguration()
{
AddDependencyResolver(new SQLiteDbDependencyResolver());
SetProviderFactory("System.Data.SQLite", SQLiteFactory.Instance);
SetProviderFactory("System.Data.SQLite.EF6", SQLiteProviderFactory.Instance);
SetProviderServices("System.Data.SQLite", (DbProviderServices)SQLiteProviderFactory.Instance.GetService(typeof(DbProviderServices)));
}
}
[DbConfigurationType(typeof(SQLiteConfiguration))]
public class SqlDbContext : DbContext
{
public SqlDbContext (string connectonString) : base(connectonString)
{
}
public DbSet<SomeClass> SomeField{ get; set; }
}
UnitTest:
[TestFixture]
class DbContextIntegrationTests
{
[Test]
public void CanReadFromMsSqlDatabase()
{
using (var context = IocContainer.Instance.Resolve<MsSqlDbContext>(someConnString))
{
Assert.DoesNotThrow(() => context.SomeField.FirstOrDefault());
}
}
[Test]
public void CanReadFromSqliteDatabase()
{
using (var context2 = IocContainer.Instance.Resolve<SqliteDbContext>(someConnString2))
{
Assert.DoesNotThrow(() => context2.Somefield.FirstOrDefault());
}
}
}
When I instantiate the above context separately by passing connection string - they both works fine.
However if they are a part of the same unit test class - they can't be run.
First context set it's provider as default (let say SQL one) and the next one DbContext (say SQLite one) can't set it's provider.
If MS SQL dbcontext goes first, then SQLite dbcontext get next exception:
System.InvalidOperationException: 'Unable to complete operation. The
supplied SqlConnection does not specify an initial catalog or
AttachDBFileName.'
If SQLite goes first, then MS SQL context gets:
System.InvalidOperationException: 'The store type 'date' could not be found in the SQLite provider manifest'
I'm just wondering what I'm missing here.
Whether it's nUnit specific (some cache).
Or it's indeed some common place for EF providers which can be set only once.
I'm not using App.config at all - just passing config string from some saved place.
#Bit #programtreasures
Found the answer.
The root cause was in inability of EF to handle multiple DBConfiguration at the same time (probably in memory) even if they are parts of different DbContexts.
More details here:
https://msdn.microsoft.com/en-us/data/jj680699
So I've just created a common context:
using System.Data.Entity.Core.Common;
using System.Data.SQLite;
using System.Data.SQLite.EF6;
namespace ClassLibrary1
{
public class commonConfig : System.Data.Entity.DbConfiguration
{
public commonConfig()
{
SetDefaultConnectionFactory(new System.Data.Entity.Infrastructure.SqlConnectionFactory());
SetProviderServices("System.Data.SqlClient", System.Data.Entity.SqlServer.SqlProviderServices.Instance);
SetProviderFactory("System.Data.SQLite", SQLiteFactory.Instance);
SetProviderServices("System.Data.SQLite", (DbProviderServices)SQLiteProviderFactory.Instance.GetService(typeof(DbProviderServices)));
SetProviderFactory("System.Data.SQLite.EF6", SQLiteProviderFactory.Instance);
}
}
}
And MS SQL DB context:
using System.Data.Entity;
using System.Data.SqlClient;
namespace ClassLibrary1
{
[DbConfigurationType(typeof(commonConfig))]
public class MsSqlDbContext : DbContext
{
public MsSqlDbContext(SqlConnection existingConnection,
bool contextOwnsConnection) : base(existingConnection, contextOwnsConnection)
{
DbConfiguration.SetConfiguration(new commonConfig());
}
public DbSet<SomeTableEntity> SomeTable { get; set; }
}
}
And SqliteDbContext:
using System.Data.Entity;
using System.Data.SQLite;
namespace ClassLibrary1
{
[DbConfigurationType(typeof(commonConfig))]
public class SqliteDbContext : DbContext
{
public SqliteDbContext(SQLiteConnection existingConnection,
bool contextOwnsConnection) : base(existingConnection, contextOwnsConnection)
{
DbConfiguration.SetConfiguration(new commonConfig());
}
public DbSet<SomeDbTableEntity> SomeTable { get; set; }
}
}
Then I can run unit test like the below:
[TestMethod]
public void TestMethod()
{
using (var context1 = new SqliteDbContext(new SQLiteConnection(
#"C:\db.sqlite"), true
))
{
Console.WriteLine("SQLITE" + Environment.NewLine);
Console.Write(context1.SomeTable.FirstOrDefault().SomeRecord);
Console.WriteLine(Environment.NewLine);
}
using (var context2 =
new MsSqlDbContext(
new SqlConnection(#"Data Source=localhost;Initial Catalog=SomeDatabase;Integrated Security=True")
, true)
)
{
Console.WriteLine("MS SQL" + Environment.NewLine);
Console.Write(context2.SomeTable.FirstOrDefault().SomeRecord);
Console.WriteLine(Environment.NewLine);
}
}
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;
}
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.
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.