MEF2 Lightweight System.Composition with Metadata - c#

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
{
}

Related

Missing dependencies for an example of using policies in StructureMap

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.

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;
}

Inconsistent accessibility with protected internal member

Attempting to make a protected internal member of a protected internal class within a public class results with the following issue:
Inconsistent accessibility: field type
'what.Class1.ProtectedInternalClass' is less accessible than field
'what.Class1.SomeDataProvider.data'
The accessibility should be equivalent, as far as I know.
Where am I mistaken?
Origination class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace what
{
public class Class1
{
// This class cannot be modified, is only
// here to produce a complete example.
public class PublicClass
{
public PublicClass() { }
}
protected internal class ProtectedInternalClass : PublicClass
{
public ProtectedInternalClass() { }
public void SomeExtraFunction() { }
}
public class SomeDataProvider
{
public int AnInterestingValue;
public int AnotherInterestingValue;
protected internal ProtectedInternalClass data; //<--- Occurs here.
public PublicClass Data { get { return data; } }
}
public static SomeDataProvider RetrieveProvider()
{
SomeDataProvider provider = new SomeDataProvider();
provider.data = new ProtectedInternalClass();
provider.data.SomeExtraFunction();
return provider;
}
}
}
Verifying protected and internal properties, same assembly:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace what
{
public class Class2 : Class1
{
public Class2()
{
var pi = new ProtectedInternalClass();
var provider = new SomeDataProvider();
provider.data = pi;
}
// no errors here
}
public class Class3
{
public Class3()
{
var pi = new Class1.ProtectedInternalClass();
var provider = new Class1.SomeDataProvider();
provider.data = pi;
}
// no errors here
}
}
Verifying protected and internal properties, different assembly:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace some_other_assembly
{
public class Class4 : what.Class1
{
public Class4()
{
var pi = new ProtectedInternalClass();
var provider = new SomeDataProvider();
provider.data = pi;
}
// no errors here
}
public class Class5
{
public Class5()
{
var pi = new what.Class1.ProtectedInternalClass(); // <--- Inaccessible due to protection level, as it should be.
var provider = new what.Class1.SomeDataProvider();
provider.data = pi; // <--- Intellisense implies inaccessible, but not indicated via error.
}
}
}
The protected applies to different classes, and this can be seen with
class Derived : what.Class1.SomeDataProvider // note: Derived is not a nested class
{
public void f()
{
var data = this.data;
}
}
in a different assembly.
this.data has to be accessible, since the class derives from SomeDataProvider. Its type, ProtectedInternalClass, is not accessible, since the class does not derive from Class1.

Can't add index via C# to RavenDB database

I'm trying to create a simple index, which includes two fields from my document: TraceRowID, LoadDate.
So I've created the following class:
using Model;
using Raven.Abstractions.Indexing;
using Raven.Client.Indexes;
using Raven.Client.Linq;
using Raven.Client.Document;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Indexes
{
public class IDLoadDateIndex : AbstractIndexCreationTask<ServiceTrace>
{
public IDLoadDateIndex()
{
Map = serviceTrace => from st in serviceTrace
select new { ID = st.TraceRowID, LoadDate = st.LoadDate };
}
}
}
My model is "ServiceTrace":
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
public class ServiceTrace
{
public int TraceRowID { get; set; }
public string StatusDescription { get; set; }
public string FullExceptionText { get; set; }
public string LoadDate { get; set; }
}
}
And there goes my DAL:
using Indexes;
using Raven.Client.Indexes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DAL
{
public class DbConnection
{
private readonly static DbConnection instance = new DbConnection();
private Raven.Client.Document.DocumentStore documentStore;
static DbConnection()
{
}
private DbConnection()
{
this.documentStore = new Raven.Client.Document.DocumentStore { Url = "http://btadmins:8080/" };
this.documentStore.Initialize();
IndexCreation.CreateIndexes(typeof(IDLoadDateIndex).Assembly, this.documentStore);
}
public static DbConnection Instance
{
get
{
return instance;
}
}
public Raven.Client.Document.DocumentStore DocumentStore
{
get
{
return documentStore;
}
}
}
}
For some reason, when stepping over on debug mode on the "CreateIndexes" line, it is done succesfully. BUT I can't see any indexes added on the RavenDB management studio silverlight app.
What can be the problem ?
Perhaps you are looking in a named database in the studio? By not naming a specific database here, you are creating the index in Raven's system database.

Getting only necessary plugins with MEF in .NET

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.

Categories