Ralph Shillington
Ralph Shillington

Reputation: 21098

Unable to Import parts with Metadata

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

Answers (1)

Adi Lester
Adi Lester

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

Related Questions