Reputation: 21098
I'm trying to import parts and include a custom MetadataAttribute, following the imperative model, using .NET 4.5
Below, I've included the simplest of example I can, which illustrates the problem.
When this code is executed, the Engine
class constructor is called, and passed an empty Enumerator, rather than the two plugins which are clearly part of the project.
At the moment I'm suspecting the PluginMetadata attribute, but I don't see how to get Metadata into the catalog without it.
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;
using System.Reflection;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var builder = new RegistrationBuilder();
builder.ForTypesDerivedFrom<IPlugIn>().Export<Lazy<IPlugIn, IPlugInMetadata>>();
builder.ForType<Engine>().Export();
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly(), builder);
var container = new CompositionContainer(catalog);
var engine = container.GetExport<Engine>();
engine.Value.Run();
}
}
internal class Engine
{
private IEnumerable<Lazy<IPlugIn, IPlugInMetadata>> PlugIns { get; set; }
public Engine(IEnumerable<Lazy<IPlugIn, IPlugInMetadata>> plugins)
{
PlugIns = plugins;
}
public void Run()
{
foreach (var plugIn in PlugIns)
{
Console.WriteLine("Starting {0}", plugIn.Metadata.Name);
plugIn.Value.Work();
}
}
}
interface IPlugIn
{
void Work();
}
interface IPlugInMetadata
{
string Name { get; }
}
[MetadataAttribute]
class PlugInMetadataAttribute : ExportAttribute, IPlugInMetadata
{
public PlugInMetadataAttribute(string name)
{
this.name = name;
}
private readonly string name;
public string Name { get { return name; } }
}
[PlugInMetadata("PlugIn1")]
class PlugIn1 : IPlugIn
{
public void Work()
{
Console.WriteLine("PlugIn 1 working");
}
}
[PlugInMetadata("PlugIn2")]
class PlugIn2 : IPlugIn
{
public void Work()
{
Console.WriteLine("PlugIn 2 working");
}
}
}
Upvotes: 0
Views: 214
Reputation: 25201
Metadata interfaces must not have any properties with setters. You should modify the IPlugInMetadata
interface so its properties won't have any setters, otherwise the composition will fail:
interface IPlugInMetadata
{
string Name { get; }
}
Also, you should consider making your PlugInMetadataAttribute
class inherit from ExportAttribute
rather than Attribute
. That will allow using this attribute as an export attribute and you won't have to use a RegistrationBuilder
.
EDIT: I think I found your problem
When trying to use ImportMany
in the constructor, you must specify so explicitly, so your constructor should look like this:
[ImportingConstructor]
public Engine([ImportMany] IEnumerable<Lazy<IPlugIn, IPlugInMetadata>> plugins)
{
PlugIns = plugins;
}
Alternatively, you can choose to import it as a property:
[ImportMany]
private IEnumerable<Lazy<IPlugIn, IPlugInMetadata>> PlugIns { get; set; }
As a side note, when deriving from ExportAttribute
, you'd like to include constructors that automatically export your part as IPlugIn
:
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
class PlugInMetadataAttribute : ExportAttribute, IPlugInMetadata
{
public PlugInMetadataAttribute()
: base(typeof(IPlugIn))
{
}
public PlugInMetadataAttribute(string contractName)
: base(contractName, typeof(IPlugIn))
{
}
public string Name { get; set; }
}
Upvotes: 1