MEF RegistrationBuilder导出特定的Interface实现

时间:2013-10-30 11:47:24

标签: c# mef composition

我有一个接口的多个实现,我想以编程方式只导出一个。我查看了RegistrationBuilder和它的AddMetaData()函数,但这是为导出定义MetaData,而不是过滤特定值。例如,我想做这样的事情:

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.

2 个答案:

答案 0 :(得分:3)

+1的问题 - 我没有机会看到MEF,因为4.5出来所以它迫使我加快了新增RegistrationBuilder课程的速度!

我猜你的例子不起作用的原因是因为我理解它,RegistrationBuilder旨在取代MEF依赖于.NET 4.0的属性的角色。 ExportMetadataAttribute是旧的做事方式的一部分,而我只是猜测旧的和新的不能很好地融合在一起。

由于添加了RegistrationBuilder,您可以完全按照自己的意愿实现,而不会让导出的类知道它们是使用MEF构建的。在我看来,这是MEF从4.0的巨大改进。

首先让我们从我们要导出的类开始。首先,我定义了MyMetadataAttribute类,它封装了与我们想要过滤的类型相关联的元数据:

public enum MyClassType
{
    TypeOne,
    TypeTwo
}

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

    public MyClassType Type { get; private set; }
}

现在来我可能想要导出的类:

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

解决问题的关键是ForTypesMatching()上的RegistrationBuilder方法。参数是一个谓词,它接受类型并返回true或false,具体取决于您是否要在导出的结果中包含该类型。下面的代码演示了一个示例:

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

答案 1 :(得分:1)

看起来不像是可以做到的。如果您从我的测试代码中注意到,RegistrationBuilder甚至不会导出使用ExportMetadata属性修饰的类(消息:System.ComponentModel.Composition警告:102:导出规范约定,适用于类型'test.MyClassA'已被源文件中应用的属性或先前约定覆盖。 MEF属性优先于RegistrationBuilder

的Fluent配置
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; }
}

更新:如果您可以将[Export(IClass)]添加到要导出的类中,则可以按如下所示过滤目录:

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