How to get a collection of exports from MEF

Tags: C#, MEF

Had fun today, learning MEF! I wanted to be able to get multiple exports available in the catalogue to be accessible through a property on a class. For example say I have a collection of log providers all added to the MEF catalogue:

   1: /// <summary>
   2: /// Provides log functionality
   3: /// </summary>
   4: public interface ILogProvider
   5: {
   6:    /// <summary>
   7:    /// Logs a message
   8:    /// </summary>
   9:    void Log(string message);
  10: }
  11:  
  12: /// <summary>
  13: /// Adam Log Provider
  14: /// </summary>
  15: [Export(typeof(ILogProvider))]
  16: public class AdamLogProvider : ILogProvider
  17: {
  18:    /// <summary>
  19:    /// Logs a message
  20:    /// </summary>
  21:    public void Log(string message)
  22:    {
  23:       // Logs the adam way
  24:    }
  25: }
  26:  
  27: /// <summary>
  28: /// Everyone Else Log Provider
  29: /// </summary>
  30: [Export(typeof(ILogProvider))]
  31: public class EveryoneElseLogProvider : ILogProvider
  32: {
  33:     /// <summary>
  34:     /// Logs a message
  35:     /// </summary>
  36:     public void Log(string message)
  37:     {
  38:         // Logs everyone else's way
  39:     }
  40: }

These both implement the ILogProvider interface and are decorated with the Export attribute so that they can be used in the composition of other classes by MEF. Now if I do this kind of call

   1: /// <summary>
   2: /// Adams class
   3: /// </summary>
   4: [Export]
   5: public class AdamClass
   6: {
   7:   /// <summary>
   8:   /// Gets or sets the log provider
   9:   /// </summary>
  10:   [Import]
  11:   public ILogProvider LogProvider { get; set; }
  12: }

I will get an exception as there are more than one ILogProvider that MEF knows about. How can I get around this?

First of all we need some way of identifying the separate log providers. As MEF uses attributes it makes sense to decorate these providers with some metadata.

   1: /// <summary>
   2: /// Metadata attribute for a log provider
   3: /// </summary>
   4: [MetadataAttribute]
   5: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
   6: public class LogProviderMetadataAttribute : ExportAttribute
   7: {
   8:     /// <summary>
   9:     /// Initializes a new instance of the LogProviderMetadataAttribute class.
  10:     /// </summary>
  11:     /// <param name="key">Represents a key that identifies what type of provider it is</param>
  12:     public LogProviderMetadataAttribute(string key) 
  13:         : base(typeof(ILogProviderMetadata))
  14:     {
  15:         this.Key = key;
  16:     }
  17:  
  18:     /// <summary>
  19:     /// Gets or sets the key
  20:     /// </summary>
  21:     public string Key
  22:     {
  23:         get;
  24:         set;
  25:     }
  26: }
  27:  
  28: /// <summary>
  29: /// Allows the definition of metadata for log providers
  30: /// </summary>
  31: public interface ILogProviderMetadata
  32: {
  33:     /// <summary>
  34:     /// Gets the provider key
  35:     /// </summary>
  36:     string Key { get; }
  37: }

So applying metadata to our providers:

   1: /// <summary>
   2: /// Adam Log Provider
   3: /// </summary>
   4: [Export(typeof(ILogProvider))]
   5: [LogProviderMetadata("Adam")]
   6: public class AdamLogProvider : ILogProvider
   7: {
   8:    /// <summary>
   9:    /// Logs a message
  10:    /// </summary>
  11:    public void Log(string message)
  12:    {
  13:       // Logs the adam way
  14:    }
  15: }
  16:  
  17: /// <summary>
  18: /// Everyone Else Log Provider
  19: /// </summary>
  20: [Export(typeof(ILogProvider))]
  21: [LogProviderMetadata("Everyone")]
  22: public class EveryoneElseLogProvider : ILogProvider
  23: {
  24:     /// <summary>
  25:     /// Logs a message
  26:     /// </summary>
  27:     public void Log(string message)
  28:     {
  29:         // Logs everyone else's way
  30:     }
  31: }

We can now use a proxy class which can pull out the appropriate provider we want by using lazy instantiation like this:

   1: /// <summary>
   2: /// Provides log provider proxy functionality
   3: /// </summary>
   4: public interface ILogProviderProxy
   5: {
   6:     /// <summary>
   7:     /// Gets the provider key
   8:     /// </summary>
   9:     string ProviderKey
  10:     {
  11:         get; set;
  12:     }
  13:  
  14:     /// <summary>
  15:     /// Get a Log Provider
  16:     /// </summary>
  17:     /// <returns>Log provider</returns>
  18:     ILogProvider GetLogProvider()
  19: }
  20:  
  21: /// <summary>
  22: /// Log provider proxy
  23: /// </summary>
  24: [Export(typeof(ILogProviderProxy))]
  25: public class LogProviderProxy : ILogProviderProxy
  26: {    
  27:     /// <summary>
  28:     /// Gets the list of providers
  29:     /// </summary>
  30:     [ImportMany(typeof(ILogProvider), AllowRecomposition = true)] 
  31:     public IList<Lazy<ILogProvider, ILogProviderMetadata>> Providers =
  32:             new List<Lazy<ILogProvider, ILogProviderMetadata>>();
  33:  
  34:     /// <summary>
  35:     /// Gets the provider key
  36:     /// </summary>
  37:     public string ProviderKey
  38:     {
  39:         get; set;
  40:     }
  41:  
  42:     /// <summary>
  43:     /// Get a Log Provider
  44:     /// </summary>
  45:     /// <returns>Log provider</returns>
  46:     public ILogProvider GetLogProvider()
  47:     {
  48:         var results = this.Providers.FirstOrDefault(p => p.Metadata.Key == this.ProviderKey);
  49:  
  50:         if (null == results)
  51:         {
  52:             throw new InvalidOperationException(
  53:                 string.Format("There are no ILogProviders with the key {0} available..", this.ProviderKey));
  54:         }
  55:  
  56:         return results.Value;
  57:     }
  58: }

So now we can get MEF to fill the collection of providers and use a proxy class to pull the specific one we wish.

Add a Comment