Search code examples
c#.netmef

MEF 2: import many


I'm use attribute-free approach to configuring MEF.
I'm read following articles:
http://msdn.microsoft.com/en-us/magazine/jj133818.aspx
http://blogs.microsoft.co.il/blogs/bnaya/archive/2013/01/12/mef-2-0-mini-series-part-4-fluent-import.aspx

Test code (console application project, .NET 4.5):

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;
using System.Linq;

namespace MEF2
{
    public interface IPlugin
    {
        void Run();
    }

    public interface IPluginMetadata
    {
        string Name { get; }
        string Version { get; }
    }

    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
    public class PluginMetadataAttribute : ExportAttribute, IPluginMetadata
    {
        public string Name { get; set; }
        public string Version { get; set; }

        public PluginMetadataAttribute(string name, string version)
            : base(typeof(IPlugin))
        {
            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
    {
        CompositionContainer container;
        IEnumerable<Lazy<IPlugin, IPluginMetadata>> plugins = Enumerable.Empty<Lazy<IPlugin, IPluginMetadata>>();

        static void Main(string[] args)
        {
            var program = new Program();

            foreach (var plugn in program.plugins) {
                Console.WriteLine("{0}, {1}", plugn.Metadata.Name, plugn.Metadata.Version);
                plugn.Value.Run();
            }
        }

        Program()
        {
            var builder = new RegistrationBuilder();
            builder
                .ForTypesDerivedFrom<IPlugin>()
                .Export<IPlugin>();
            builder
                .ForType<Program>()
                .Export()
                .ImportProperties<IPlugin>(
                    propertyFilter => true,
                    (propertyInfo, importBuilder) => {
                        importBuilder.AsMany();
                    }
                );

            var catalog = new AggregateCatalog(
                new AssemblyCatalog(typeof(Program).Assembly, builder)
            );

            container = new CompositionContainer(catalog);
            container.ComposeParts(this);
        }
    }
}

Exports works fine.
But when I try import many it doesn't work.
Please help me solve this problem.


Solution

  • This is not an attribute free (imperative) approach since the export is attributed (declarative). Your custom metadata attribute derives from ExportAttribute.

    For this code to work you need to do the following:

    • Remove the imperative export from the constructor code.
    • Pass the registration builder to the AssemblyCatalog. Without this the registration builder cannot be used.
    • Update the convention-based import with Lazy<IPlugin, IPluginMetadata> since this is what is being exported.
    • Replace the call to ComposeParts with GetExports.

    The updated constructor code is:

                var builder = new RegistrationBuilder();
    
                builder
                    .ForType<Program>()
                    .Export()                
                    .ImportProperties<Lazy<IPlugin, IPluginMetadata>>(
                        propertyFilter => true,
                        (propertyInfo, importBuilder) =>
                        {
                            importBuilder.AsMany();
                        }
                    );
    
                var catalog = new AggregateCatalog(
                    new AssemblyCatalog(typeof(Program).Assembly, builder)
                );
    
                container = new CompositionContainer(catalog);
                //container.ComposeParts(this);
                plugins = container.GetExports<IPlugin, IPluginMetadata>();
    

    I'm not sure how to make this work with ComposeParts yet. I will have a look at it. Also the custom metadata does not have to derive from ExportAttribute. It can derive from System.Attribute. This will let you have a imperative export as well.