How to get a collection of exports from 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.