Question

I have multiple implementations of an interface and I want to programatically export only one. I have looked at RegistrationBuilder and it's AddMetaData() function but this is defining MetaData for the export, rather than filtering for a specific value. For example, I want to do something like this:

public enum MyClassType { TypeA, TypeB }
public interface IClass {}
public interface ClassMetaData { MyClassType Type { get; } }

[ExportMetadata("Type", MyClassType.TypeA)]
public MyClassA : IClass
{
    public MyClassType Type { get { return MyClassType.TypeA; } }
}

[ExportMetadata("Type", MyClassType.TypeB)]
public MyClassB : IClass
{
    public MyClassType Type { get { return MyClassType.TypeB; } }
}

//...Then in my bootstrapping class where I set up the MEF container...

var registrationBuilder = new RegistrationBuilder();
registrationBuilder.ForTypesDerivesFrom<IClass>()....
// How do I specify a filter in ^ to say only export the implementation with MetaData.Type == MyClassA or instance.Type == MyClassA.
Was it helpful?

Solution

+1 for the question - I hadn't had a chance to look at MEF since 4.5 came out so it forced me to get up to speed with the newly added RegistrationBuilder class!

I'm guessing the reason your example doesn't work is because as I understand it, the RegistrationBuilder is designed to replace the role of attributes that MEF relies on so heavily up to .NET 4.0. The ExportMetadataAttribute is part of the old way of doing things, and it's just my guess that the old and new don't play well together.

Thanks to the addition of RegistrationBuilder you can achieve exactly what you want without the exported classes having any knowledge that they are being constructed using MEF. In my opinion this is a vast improvement in MEF from 4.0.

First let us start with the classes we want to export. First of all I define the MyMetadataAttribute class, which encapsulates the metadata associated to the type we will want to filter on:

public enum MyClassType
{
    TypeOne,
    TypeTwo
}

[AttributeUsage(AttributeTargets.Class)]
public class MyMetadataAttribute: Attribute
{
    public MyMetadataAttribute(MyClassType type)
    {
        Type = type;
    }

    public MyClassType Type { get; private set; }
}

Now come the classes that I potentially want to export:

public interface IClass
{
}

[MyMetadata(MyClassType.TypeOne)]
public class MyClassA : IClass
{
    public MyClassType Type
    {
        get { return MyClassType.TypeOne; }
    }
}

[MyMetadata(MyClassType.TypeTwo)]
public class MyClassB : IClass
{
    public MyClassType Type
    {
        get { return MyClassType.TypeTwo; }
    }
}

The key to solving your problem is the ForTypesMatching() method on RegistrationBuilder. The argument is a predicate that takes the type and returns true or false depending on whether you want to include the type in the exported results. The code below demonstrates an example of this:

internal class Program
{
    private static void Main(string[] args)
    {
        var registrationBuilder = new RegistrationBuilder();
        registrationBuilder
            .ForTypesMatching<IClass>(t => FilterOnMetadata(t, MyClassType.TypeOne))
            .ExportInterfaces();
        var assemblyCatalog = new AssemblyCatalog(typeof (MyClassType).Assembly, registrationBuilder);
        var compositionContainer = new CompositionContainer(assemblyCatalog);
        var ic = new TestImportContainer();
        compositionContainer.ComposeParts(ic);
        var count = ic.ImportedParts.Count();
    }

    public static bool FilterOnMetadata(Type t, MyClassType classType)
    {
        var metadataAttribute = 
            (MyMetadataAttribute) t.GetCustomAttributes(true)
                .SingleOrDefault(at => at is MyMetadataAttribute);
        if (metadataAttribute != null)
            return metadataAttribute.Type == classType;
        return false;
    }

    private sealed class TestImportContainer
    {
        [ImportMany(typeof(IClass))]
        public IEnumerable<IClass> ImportedParts { get; set; }
    }
}

OTHER TIPS

Doesn't look like it can be done. If you'll notice from my test code, RegistrationBuilder won't even export a class that is decorated with a ExportMetadata attribute (message: System.ComponentModel.Composition Warning: 102 : An Export specification convention that would apply to type 'test.MyClassA' has been overridden by attributes applied in the source file or by a prior convention). MEF attributes take precedence over Fluent configuration via RegistrationBuilder

class Program
{
    static void Main()
    {
        // importing class instance
        var mcc = new MyClassConsumer();

        // type to export
        var typeToExport = typeof(MyClassA);

        Console.WriteLine("Type to export: {0}", typeToExport);

        var rb = new RegistrationBuilder();

        rb.ForType(typeToExport)
            .ExportInterfaces();

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

        var container = new CompositionContainer(catalog);

        container.ComposeParts(mcc); // bombs if MyClassA's MetadataAttribute is not commented out

        Console.WriteLine("Imported property's type: {0}", mcc.MyClass.GetType());

        Console.ReadLine();
    }
}

public enum MyClassType { TypeA, TypeB }
public interface IClass {}
public interface IClassMetaData { MyClassType Type { get; } }

[ExportMetadata("Type", MyClassType.TypeA)] // works if you comment this line
public class MyClassA : IClass
{
}

[ExportMetadata("Type", MyClassType.TypeB)]
public class MyClassB : IClass
{
}

public class MyClassConsumer
{
    [Import]
    public IClass MyClass { get; set; }
}

UPDATE: if you can add [Export(IClass)] to the classes you'd like to export, you can filter the catalog as shown below:

class Program
{
    static void Main()
    {
        var catalog = new AssemblyCatalog(typeof(Program).Assembly);

        var filteredCatalog = catalog.Filter(p =>
            {
                var type = ReflectionModelServices.GetPartType(p).Value;

                return typeof(IClass).IsAssignableFrom( type ) && // implements interface you're looking for
                    Attribute.IsDefined(type, typeof(ExportMetadataAttribute)) && // has ExportMetadata attribute
                    // check for Type == MyClassType.TypeA
                    type.GetCustomAttributes(typeof(ExportMetadataAttribute), true).Any(ca =>
                        {
                            var ema = (ExportMetadataAttribute)ca;

                            return ema.Name == "Type" && (MyClassType)ema.Value == MyClassType.TypeA;
                        });
            });

        var container = new CompositionContainer(filteredCatalog);

        MyClassConsumer mcc = new MyClassConsumer();

        container.ComposeParts(mcc);

        Console.WriteLine("Imported property's type: {0}", mcc.MyClass.GetType());

        Console.ReadLine();
    }
}

public enum MyClassType { TypeA, TypeB }
public interface IClass {}
public interface IClassMetaData { MyClassType Type { get; } }

[Export(typeof(IClass))]
[ExportMetadata("Type", MyClassType.TypeA)]
public class MyClassA : IClass
{
}

[Export(typeof(IClass))]
[ExportMetadata("Type", MyClassType.TypeB)]
public class MyClassB : IClass
{
}

public class MyClassConsumer
{
    [Import]
    public IClass MyClass { get; set; }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top