Tuesday, July 20, 2010

Differentiating MEF Metadata Using Custom Attributes

Discovered something interesting about MEF today when resolving exports and their associated metadata using custom attributes.  Lets say you have two different types of metadata you want to strongly type by using 2 custom attributes and associated metadata interfaces like so:

   1: public interface ILibrarySectionMetadata



   2: {



   3:     string Title



   4:     {



   5:         get;



   6:     }



   7: }



   8:  



   9: public interface ILiveViewMetadata



  10: {



  11:     string Title



  12:     {



  13:         get;



  14:     }



  15: }



  16:  



  17: [MetadataAttribute]



  18: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]



  19: public class LibrarySectionAttribute : ExportAttribute, ILibrarySectionMetadata



  20: {



  21:     public LibrarySectionAttribute(string title) : base(typeof(UserControl))



  22:     {



  23:         this.Title = title;



  24:     }



  25:  



  26:     public string Title



  27:     {



  28:         get;



  29:         private set;



  30:     }



  31: }



  32:  



  33: [MetadataAttribute]



  34: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]



  35: public class LiveViewAttribute : ExportAttribute, ILiveViewMetadata



  36: {



  37:     public LiveViewAttribute(string title) : base(typeof(UserControl))



  38:     {



  39:         this.Title = title;



  40:     }



  41:  



  42:     public string Title



  43:     {



  44:         get;



  45:         private set;



  46:     }



  47: }




In other words, the two sets of metadata expose the same sets of properties, but are differentiated by their strongly typed class representations.  Now i expected that this meant i would be able to differentiate the two by requesting (importing) metadata of a certain type, for example the following would only retrieve library sections (ie UserControls decorated with the [LibrarySection] attribute):





   1: [ImportMany]



   2: public List<Lazy<UserControl, ILibrarySectionMetadata>> Sections



   3: {



   4:     get;



   5:     set;



   6: }


It turns out however, that MEF is still using reflection behind the scenes to resolve the metadata, because it will resolve ANY UserControl that is decorated with either attribute, as both have the necessary properties allowing them to be mapped to either metadata type.  This means that you need to differentiate the UserControls with a string identifier in both the attribute and the [Import] declaration to allow them to be differentiated when importing, like so:




   1: [MetadataAttribute]



   2: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]



   3: public class LibrarySectionAttribute : ExportAttribute, ILibrarySectionMetadata



   4: {



   5:     public LibrarySectionAttribute(string title) : base("LibrarySection", typeof(UserControl))



   6:     {



   7:         this.Title = title;



   8:     }



   9:  



  10:     public string Title



  11:     {



  12:         get;



  13:         private set;



  14:     }



  15: }



  16:  



  17: [ImportMany("LibrarySection")]



  18: public ObservableCollection<Lazy<UserControl, ILibrarySectionMetadata>> Sections



  19: {



  20:     get;



  21:     private set;



  22: }


This is unfortunate (if not a minor problem) because it dirties the consumption class with a magic string, but i dont see another way of achieving the same result when the exported types use contracts that match (in this example, all items exported as UserControls).  This of course also holds true if you are trying to import types with metadata that is a subset of another metadata interface.  For example if live views also had a Description property, then importing library sections would still match with live view UserControls because they provide the Title property necessary for MEF to resolve the library section metadata.

No comments: