Search code examples
c#interfacemef

How to set ExportMetaData with multiple values as well as single w/ custom attribute?


I have the following ExportMetaData attributes set on my class:

  [Export(typeof(IDocumentViewer))]
  [ExportMetadata("Name", "MyViewer")]
  [ExportMetadata("SupportsEditing", true)]
  [ExportMetadata("Formats", DocFormat.DOC, IsMultiple = true)]
  [ExportMetadata("Formats", DocFormat.DOCX, IsMultiple = true)]
  [ExportMetadata("Formats", DocFormat.RTF, IsMultiple = true)]  

I also have a supporting interface:

  public interface IDocumentViewerMetaData {
    /// <summary>
    /// Gets the format.
    /// </summary>
    /// <value>The format.</value>
    IEnumerable<DocFormat> Formats { get; }
    /// <summary>
    /// Gets the name of the viewer
    /// </summary>
    /// <value>The name.</value>
    string Name { get; }
    /// <summary>
    /// Gets a value indicating whether this viewer supports editing
    /// </summary>
    /// <value><c>true</c> if [supports editing]; otherwise, <c>false</c>.</value>
    bool SupportsEditing { get; }
  }

And of course my ImportMany:

[ImportMany(typeof(IDocumentViewer))]
public IEnumerable<Lazy<IDocumentViewer, IDocumentViewerMetaData>> _viewers { get; set; }

What I would like to do is use a strongly-typed attribute class instead of using the ExportMetaData attribute. I have not figured out a way to do this while also supporting single values (Name, SupportsEditing, in the example above).

I envision doing something similiar the following (or whatever is suggested as best):

[Export(typeof(IDocumentViewer))]
[DocumentViewerMetadata(Name = "MyViewer")]
[DocumentViewerMetadata(SupportsEditing = true)]
[DocumentViewerMetadata(Format = DocFormat.DOC)]
[DocumentViewerMetadata(Format = DocFormat.DOCX)]

I am fairly certain that there IS a way to do this, I just haven't found the right way to connect the dots. :)


Solution

  • You can subclass the ExportAttribute with your own implementation, and decorate it with a MetadataAttribute to allow MEF to use its properties to project the metadata proxy it uses during composition:

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property),
     MetadataAttribute]
    public class ExportDocumentViewerAttribute : ExportAttribute, IDocumentViewerMetadata
    {
      public ExportDocumentViewer(string name, bool supportsEditing, params DocFormat[] formats)
        : base(typeof(IDocumentViewer))
      {
        if (string.IsNullOrEmpty(name))
          throw new ArgumentException("Export requires a name", "name");
    
        Name = name;
        SupportsEditing = supportsEditing;
        Formats = formats ?? Enumerable.Empty<DocFormat>();
      }
    
      public string Name { get; private set; }
    
      public bool SupportsEditing { get; private set; }
    
      public IEnumerable<DocFormat> Formats { get; private set; }
    }
    
    [ExportDocumentViewer("Word", true, DocFormat.DOC, DocFormat.DOCX)]
    public WordDocumentViewer : IDocumentViewer
    {
      // Stuff
    }
    

    Note you don't actually need to decorate it with your IDocumentViewerMetadata contract, as MEF will project it regardless, I just prefer to so that I know if I make changes to the metadata contract, that my custom export attribute conforms.