How to use MEF Inherited Export & MetaData?
Asked Answered
R

3

15

I have an interface:

[InheritedExport(typeof(IMetric))]
public interface IMetric { ... }

I have a Meta attribute interface:

 public interface IMetricAttribute { ... }

and an attribute that implements it:

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MetricAttribute : ExportAttribute, IMetricAttribute {
    public string MetricName { get; set; }
    public string MetricDescription { get; set; }

    public MetricAttribute(string name, string description)
        : base(typeof(MetricAttribute)) {
        this.MetricName = name;
        this.MetricDescription = description;
    }
}

I then have two classes:

[Metric("MetricA","MetricA")]
public class MetricA: IMetric { ... }

[Export(typeof(IMetric))] <<<< THIS IS IMPORTANT
[Metric("MetricB", "MetricB")]
public class MetricB: IMetric { ... }

I then try to import the metrics ( i can see both in the cataloge)

The following returns be MetricA AND MetricB

var metrics = compositionContainer.GetExports<IMetric>();

However the following returns ONLY MetricB and NOT MetricA

var metrics = compositionContainer.GetExports<IMetric, IMetricAttribute>();

any idea why?

(note the duplicate export on MetricB (it already has it from implementing IMetric))

thanks

David

Refutative answered 4/7, 2011 at 11:37 Comment(4)
+1 Interesting question, looking forward to see the answer. Wouldn't it be possible to work around this issue by the following: Instead of using InheritedExport on IMetric you could use only the MetricAttribute as you anyway have to declare it on your deriving types. At MetricAttribute ctor you could call base(typeof(IMetric)). You haven't provided further information on the MetricAttribute implementation, maybe you're already doing this there.Rhaetian
@ba_friend - thanks I've tried your suggestion which worked nicely though I'm not quite sure why? I'm still interested to find out why the original code didn't workRefutative
Me either, i would like to try it myself but thats not possible at the moment.Rhaetian
Came from codeproject.com/Articles/376033/…Lectern
E
14

First time I've seen this behaviour, but from what I can understand, metadata is generated per-export at the type level. So, given:

[Metric("MetricA", "MetricA")]
public class MetricA : IMetric
{

}

You have two exports for this type. You have the export of MetricA which is implictly provided by your MetricAttribute, and you have the inherited export for IMetric provided by the InheritedExport(typeof(IMetric)) attribute on your interface.

If you look at the container, you'll notice two exports defined for MetricA. Here is the first, with its metadata:

enter image description here

And here is the second:

enter image description here

You'll notice that the metadata is done on the export of MetricA, not the inherited export. If I added a further export, lets say [Export("test")] to MetricA, you get another export definition, with the same metadata items for MetricName and MetricDescription for the contract named "test". This shows you that as the type is analysed, the export attribute is identified, and the export definition that is created includes the metadata specified at the same level in the abstraction tree.

The easiest way to do what you want, is to drop out the InheritedExport, and modify your definition of your MetricAttribute to:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false), MetadataAttribute]
public class MetricAttribute : ExportAttribute, IMetricAttribute
{
    public MetricAttribute(string name, string description)
        : base(typeof(IMetric))
    {
        this.MetricName = name;
        this.MetricDescription = description;
    }

    public string MetricName { get; private set; }
    public string MetricDescription { get; private set; }

}

Where you then pass in typeof(IMetric) to the base ExportAttribute constructor. You then correctly get the two exports for GetExports<IMetric>() and GetExports<IMetric, IMetricAttribute>().

Espalier answered 6/7, 2011 at 13:34 Comment(1)
thank you for your detailed answer. I think i see what you mean - to summarise: the problem is that metadata is linked to exports, however the inheritedexport doesn't seem to link to the meta data on implemented classes (a strange design decision!). One solution is then as ba_friend said to get the attribute to do an export so that the export and the metadata are linked. I will accept your answer though i do wonder whether it is possible to use inherited export together with metadata - but maybe that is another question i should post! Thanks again :)Refutative
S
3

I came across the same problem and found a differen solution which worked fine for me: I just added metadata to the interface!

[InheritedExport(typeof(IMetric))]
[Metric("name","description")]
public interface IMetric { ... }

You can leave the fields blank or use null as default, but it is important to specify the Metadata here. Then you specify your classes without export attribute:

[Metric("MetricA")]
public class MetricA: IMetric { ... }

Be aware that you can can specify just one metadata, but the second one won't be description in this case, it will be null! So the metadata in the interface are NOT default values. All in all this worked for me and I can use InheritedExport with my metadata :-)

Selfknowledge answered 17/2, 2013 at 18:35 Comment(1)
You can get around the "just one metadata" problem by using attributes derived from MetadataAttribute. But they work only when applied to the final exported class; that is, they cannot be inherited - stupid design decision.Naturally
G
2

To clarify on Matthew answer:

When you are defining custom metadata attribute class MetricAttribute and inheriting from ExportAttribute, you are essentially adding [Export] attribute to all classes that you decorate with your [Metric] attribute. This means that you don't need [InheritedExport] attribute on the interface anymore as it just creates separate export definition without any metadata.

If you want to create a more reusable metadata attribute, you can expose ExportAttribute constructor parameters in your MetricAttribute as follows:

public MetricAttribute(Type contractType, string name, string description)
    : base(contractType)
{
    this.MetricName = name;
    this.MetricDescription = description;
}

By introducing contractType variable you can now supplement your definition of

[Export(typeof(IMetric))]
[Metric("MetricB", "MetricB")]
public class MetricB: IMetric { ... }

with:

[Metric(typeof(IMetric), "MetricB", "MetricB")]
public class MetricB: IMetric { ... }
Genetic answered 11/6, 2015 at 8:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.