Search code examples
c#metadatamef

Export several MEF Metadata attributes


Good day, can you please assist me in this complex task. I developing module app with MEF. Each module have metadata like this:

    [MetadataAttribute]
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
        public abstract class ModuleMetadata : ExportAttribute, IArmModuleMetadata
        {
            private ModuleDescriptor _descriptor;

            public ModuleMetadata(string name, string code, string category, string iconUri)
                : base()
            {
                _descriptor = new ModuleDescriptor(name, code, category, iconUri);               
     }
}

I use it like this:

 [Export(typeof(IArmTaskModule))]
[TaskModuleMetadata("test1", "code",
     @"pack://application:,,,/WpfVisualModule;component/Icons/chart_line_error.png",
     "road_weather_stations",
     TargetItem = TargetItems.ControlComplex)]
class AdvancedChartContract : Burstroy.Arm.Contracts.IArmTaskModule

For each module there is a set of properties generated by Dictionary<string, Settings.ParamDescriptor> CreateSettingsBlock() method in IArmModule, where Key contains code of property and Value contains fancy name and default value.

In my main app i use Lazy<T, TMetadata> for importing modules like this

[ImportMany(typeof(IArmTaskModule), AllowRecomposition = true)]
private IEnumerable<Lazy<IArmTaskModule, IArmTaskModuleMetadata>> _taskModules;

The problem in this approach that Lazy<T, TMetadata> will create instance of IArmTaskModule for recieving settings block from method. I want to prevent itby adding properties info to metadata. I tried to extend attrribute constructor with new List () but it's failed (attribute restriction), i also tried to make new attribute

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ExportedParam : ExportAttribute, IArmModuleProperty
{
    public ExportedParam(string code, string fancyName) 
        : base()
    {
        this.Code = code;
        this.FancyName = fancyName;
        //this.Value = value;
    }

    public string Code { get; private set; }
    public string FancyName { get; private set; }
    public object Value { get; private set; }
}

but it's failed either.

[ExportedParam("a", "b")]
[ExportedParam("b", "c")]
[ExportMetadata("fffffuuu", 2)]
class MeteoSummary : IArmVisualModule, 

Does anyone have any suggestions?


Solution

  • The ModuleMetadata class does not follow the custom export attribute guidelines.

    It should be like:

        [MetadataAttribute]
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
        public abstract class ModuleMetadataExportAttribute : ExportAttribute
        {
            public ModuleDescriptor Descriptor { get; private set; }
    
            public ModuleMetadata(string name, string code, string category, string iconUri)
                : base(typeof(IArmTaskModule))
            {
                Descriptor= new ModuleDescriptor(name, code, category, iconUri);               
            }
         }
    

    And your metadata interface like this:

    public interface IArmModuleMetadata
    {
        ModuleDescriptor Descriptor { get; }
    }
    

    Note that:

    • You don't have to make your custom export attribute implement the metadata interface. MEF will handle this.
    • You need to pass the exported type to the ExportAttribute's constuctor.
    • You add all public properties of the custom export attribute to the inteface.

    I also changed the name of the custom attribute class to conform with the guidelines for creating custom attributes (I cannot find a source for this right now but you could check the Framework Design Guidelines 2nd edition by Cwalina, Abrams). This is not necessary though.

    Then you export like this:

    [ModuleMetadataExport(...))] //Add your params here.
    [TaskModuleMetadata("test1", "code",
         @"pack://application:,,,/WpfVisualModule;component/Icons/chart_line_error.png",
         "road_weather_stations",
         TargetItem = TargetItems.ControlComplex)]
    class AdvancedChartContract : Burstroy.Arm.Contracts.IArmTaskModule
    

    I left TaskModuleMetadata because I don't know what it is. I assume it doesn't have to do with MEF.

    Finally do keep the import part unchanged. Then when you iterate over the _taskModules sequence you will access the Value property only after you have checked the Metadata property and made sure that the current module is the one you want. Then you access the Value property and the Module will be created.