Posterous theme by Cory Watilo

Creating a MEF composition container with the ability to filter exports

When I first started exploring the metadata capabilities of MEF, I soon found myself wanting to, not only, restrict what metadata I wanted the exports to have but also the value of the metadata. Early attempts included crude solutions such as importing to a private member, and then having a public member which applied the filtering for me. I didn’t like it then and I don’t like it now. I would force me to write more code and clutter up my classes.


I’ve seen the question of metadata value filtering surface a couple of time on the MEF discussion list and when I came across it the other day I decided to give it another stab. My first solution included writing a custom export provider to filer exports, but this means it would only work to filter out exports which originates from other export providers which I control, since the CompositionContainer class off MEF creates a couple of export providers internally and there’s no way of wrapping them up in a filter.


So the next step was to create a custom composition container which had filter capabilities.

1: /// <summary>  2: /// Defines a <see cref="CompositionContainer"/> with the capability of applying a condition on exports  3: /// which matches any of the registered contracts.  4: /// </summary>  5: public class ConditionalCompositionContainer : CompositionContainer  6: {  7:     /// <summary>  8:     /// Initializes a new instance of the <see cref="ConditionalCompositionContainer"/> class, using  9:     /// the specified <see cref="CompositionContainer"/>. 10:     /// </summary> 11:     /// <param name="catalog">The catalog to add to the container.</param> 12:     public ConditionalCompositionContainer(ComposablePartCatalog catalog) 13:         : base(catalog) 14:     { 15:         this.Filters = new Dictionary<string, Func<Export, bool>>(); 16:     } 17:  18:     /// <summary> 19:     /// Gets or sets filters which are availble to the container. 20:     /// </summary> 21:     /// <value> 22:     /// A <see cref="IDictionary{TKey,TValue}"/> object. The of the dictionairy should  23:     /// be the contract name. 24:     /// </value> 25:     protected IDictionary<string, Func<Export, bool>> Filters { get; set; } 26:  27:     /// <summary> 28:     /// Registers a contract as a filter. 29:     /// </summary> 30:     /// <param name="contractName">The name of the contract to register.</param> 31:     /// <returns>A <see cref="ConditionalCompositionContainer"/> object.</returns> 32:     public virtual ConditionalCompositionContainer Register(string contractName) 33:     { 34:         this.Filters.Add(contractName, null); 35:         return this; 36:     } 37:  38:     /// <summary> 39:     /// Registers a contract as a filter. 40:     /// </summary> 41:     /// <param name="contractType">The <see cref="Type"/> of the contract to register.</param> 42:     /// <returns>A <see cref="ConditionalCompositionContainer"/> object.</returns> 43:     public virtual ConditionalCompositionContainer Register(Type contractType) 44:     { 45:         return this.Register(contractType.Name); 46:     } 47:  48:     /// <summary> 49:     /// Assigns a condition to the previously registered contract. 50:     /// </summary> 51:     /// <param name="condition"> 52:     /// A <see cref="Func{T,TResult}"/> object containing the condition  53:     /// to apply to the contract. 54:     /// </param> 55:     /// <returns>A <see cref="ConditionalCompositionContainer"/> object.</returns> 56:     public virtual ConditionalCompositionContainer Where(Func<Export, bool> condition) 57:     { 58:         this.Filters[this.Filters.Last().Key] = condition; 59:         return this; 60:     } 61:  62:     /// <summary> 63:     /// Gets the exports which matches the provided <see cref="ImportDefinition"/>. 64:     /// </summary> 65:     /// <param name="definition">The import definition to find exports for.</param> 66:     /// <returns>A <see cref="IEnumerable{T}"/> object, containing the matched exports.</returns> 67:     protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition) 68:     { 69:         ContractBasedImportDefinition import = 70:             definition as ContractBasedImportDefinition; 71:  72:         if (import != null) 73:         { 74:             Func<Export, bool> condition; 75:             if (this.Filters.TryGetValue(import.ContractName, out condition)) 76:             { 77:                 return base.GetExportsCore(definition).Where(condition); 78:             } 79:         } 80:  81:         return base.GetExportsCore(definition); 82:     } 83: }

This enables you to register conditions on a specific contract, using a lambda expression and a small fluent API. For example you could do something like this

1: private void Compose()  2: {  3:     var catalog =  4:         new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());  5:   6:     var batch =  7:         new CompositionBatch();  8:     batch.AddPart(this);  9:  10:     Dictionary<string, object> expectedMetadata = 11:         new Dictionary<string, object> 12:             { 13:                 { "Foo", 10}, 14:                 { "Bar", 1 } 15:             }; 16:  17:     var container = 18:         new ConditionalCompositionContainer(catalog); 19:  20:     container 21:         .Register("TestMethod") 22:         .Where(e => expectedMetadata.All(m => e.Metadata.ContainsKeyWithValue(m.Key, m.Value))); 23:      24:     container.Compose(batch); 25: }

To restricts exports, with the contract name of TestMethod to only be imported if they contained the metadata names Foo and Bar with the values of 10 and 1. You could take it a step further and make sure that metadata is within a valid value range and so on. The ContainsKeyWithValue method is a simple extension method on IDictionairy


1: public static class IDictionaryExtensions  2: {  3:     public static bool ContainsKeyWithValue(this IDictionary<string, object> dictionary, string key, object value)  4:     {  5:         if (dictionary.ContainsKey(key))  6:         {  7:             return dictionary[key].Equals(value);  8:         }  9:  10:         return false; 11:     } 12: }

Please keep in mind that the ConditionalCompositionContainer class could do with some more attention before it’s put into production code. Better exception handling is one area which could do with soem improvements before you put it to use.