Search code examples
c#mefmef2

Why does MEF2 not apply metadata attributes to all part exports?


I'm trying to port a collection of .NET Framework-based applications to .NET Core, and as part of this process I need to switch from using MEF1 to MEF2. I've been having a lot of difficulty wrapping my head around issues relating to MEF2 (though I've found this post really helpful), but I've recently stumbled across the reason behind one of them I've been having.

In particular, I have a number of classes that export metadata using a custom ExportAttribute and I would like to import them all in another class and filter them based on this metadata. This was all working fine in MEF1 but in MEF2 I ran up against problems such as "Export metadata for x is missing and no default value was supplied.".

More specifically, I annotate my exported classes like below:

[Export(typeof(IClientRequestProcessor<RelaySystemModel>))]
[TargetDevice("<<Foo>>")]
internal class RelaySystemClientRequestProcessor : IClientRequestProcessor<RelaySystemModel>
{
}

Then elsewhere, I will try to import them like this:

[ImportMany]
public IEnumerable<ExportFactory<IClientRequestProcessor<RelaySystemModel>, DeviceSpecific>> RelayRequestProcessors { private get; set; }

And then, on satisfaction of imports, attempt to filter them by metadata:

private static IEnumerable<ExportFactory<T, DeviceSpecific>> FilterForFoo<T>(IEnumerable<ExportFactory<T, DeviceSpecific>> items)
{
    return from it in items where it.Metadata.DeviceId == "<<Foo>>" select it;
}

Where TargetDeviceAttribute is defined as follows:

[MetadataAttribute, AttributeUsage(AttributeTargets.Class)]
public class TargetDeviceAttribute : ExportAttribute, IDeviceSpecific
{
    public TargetDeviceAttribute(string deviceId)
    {
        this.DeviceId = deviceId;
    }

    public string DeviceId { get; private set; }
}

I've found that what's happening is that the part RelaySystemClientRequestProcessor corresponds with two exports: IClientRequestProcessor<RelaySystemModel>, which is the export I'm interested in and the interface I try to import the part with, and RelaySystemClientRequestProcessor. However, the "DeviceId" metadata is only associated with the latter and not the former, which isn't helpful.

MEF2-exports

There are several ways that I believe this could be resolved though I haven't fully tested:

  1. Applying the attribute ExportMetadata("DeviceId", "<<foo>>") to all of my exported parts.

  2. Changing the TargetDeviceAttribute to use the constructor public TargetDeviceAttribute(string deviceId, Type exportType) : base(exportType).

I'm not in favour of these solutions; the former would be problematic in case I wanted to change the metadata key, and both would involve changing the way I export all my parts.

What I'm wondering is if MEF2 provides a way to export metadata like in MEF1: by creating a custom metadata attribute and having it apply that metadata to all exports associated with a part. Is this possible?


Solution

  • Turns out I just needed to delete 6 characters. Instead of making TargetDeviceAttribute inherit from ExportAttribute, it should just inherit from Attribute instead:

    [MetadataAttribute, AttributeUsage(AttributeTargets.Class)]
    public class TargetDeviceAttribute : Attribute, IDeviceSpecific
    {
        public TargetDeviceAttribute(string deviceId)
        {
            this.DeviceId = deviceId;
        }
    
        public string DeviceId { get; private set; }
    }
    

    In the more general case, this means that any metadata that can be associated with multiple possible types but should ensure better static type safety/maintainability than just ExportAttribute("foo", "bar") should I suppose be done something like below:

    public interface IMetadataExtension
    {
        string Foo { get; }
    }
    
    public class MetadataExtension : IMetadataExtension
    {
        public string Foo { get; set; }
    }
    
    [MetadataAttribute]
    public class MetadataExtensionAttribute : Attribute, IMetadataExtension
    {
        public MetadataExtensionAttribute(string foo)
        {
            Foo = foo;
        }
    
        public string Foo { get; }
    }
    
    [Export]
    [MetadataExtension("bar")]
    public class SomeExport
    {
    
    }