1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.Composition.Hosting; 8 using System.Composition.Hosting.Core; 9 using System.Linq; 10 using System.Reflection; 11 using Microsoft.Composition.Demos.ExtendedCollectionImports.Util; 12 13 namespace Microsoft.Composition.Demos.ExtendedCollectionImports.Dictionaries 14 { 15 public class DictionaryExportDescriptorProvider : ExportDescriptorProvider 16 { 17 /// <summary> 18 /// Identifies the metadata used as key for a dictionary import. 19 /// </summary> 20 private const string KeyByMetadataImportMetadataConstraintName = "KeyMetadataName"; 21 22 private static readonly MethodInfo s_getDictionaryDefinitionsMethod = typeof(DictionaryExportDescriptorProvider).GetTypeInfo().GetDeclaredMethod("GetDictionaryDefinition"); 23 GetExportDescriptors(CompositionContract contract, DependencyAccessor descriptorAccessor)24 public override IEnumerable<ExportDescriptorPromise> GetExportDescriptors(CompositionContract contract, DependencyAccessor descriptorAccessor) 25 { 26 if (!(contract.ContractType.IsConstructedGenericType && contract.ContractType.GetGenericTypeDefinition() == typeof(IDictionary<,>))) 27 return NoExportDescriptors; 28 29 CompositionContract unwrapped; 30 string keyByMetadataName; 31 if (!contract.TryUnwrapMetadataConstraint(KeyByMetadataImportMetadataConstraintName, out keyByMetadataName, out unwrapped)) 32 return NoExportDescriptors; 33 34 var args = contract.ContractType.GenericTypeArguments; 35 var keyType = args[0]; 36 var valueType = args[1]; 37 38 var valueContract = unwrapped.ChangeType(valueType); 39 40 var gdd = s_getDictionaryDefinitionsMethod.MakeGenericMethod(keyType, valueType); 41 42 return new[] { (ExportDescriptorPromise)gdd.Invoke(null, new object[] { contract, valueContract, descriptorAccessor, keyByMetadataName }) }; 43 } 44 GetDictionaryDefinition(CompositionContract dictionaryContract, CompositionContract valueContract, DependencyAccessor definitionAccessor, string keyByMetadataName)45 private static ExportDescriptorPromise GetDictionaryDefinition<TKey, TValue>(CompositionContract dictionaryContract, CompositionContract valueContract, DependencyAccessor definitionAccessor, string keyByMetadataName) 46 { 47 return new ExportDescriptorPromise( 48 dictionaryContract, 49 typeof(IDictionary<TKey, TValue>).Name, 50 false, 51 () => definitionAccessor.ResolveDependencies("value", valueContract, true), 52 deps => 53 { 54 var items = deps.Select(d => Tuple.Create(d.Target.Origin, d.Target.GetDescriptor())).ToArray(); 55 var isValidated = false; 56 return ExportDescriptor.Create((c, o) => 57 { 58 if (!isValidated) 59 { 60 Validate<TKey>(items, keyByMetadataName); 61 isValidated = true; 62 } 63 64 return items.ToDictionary( 65 item => (TKey)item.Item2.Metadata[keyByMetadataName], 66 item => (TValue)item.Item2.Activator(c, o)); 67 }, 68 NoMetadata); 69 }); 70 } 71 Validate(Tuple<string, ExportDescriptor>[] partsWithMatchedDescriptors, string keyByMetadataName)72 private static void Validate<TKey>(Tuple<string, ExportDescriptor>[] partsWithMatchedDescriptors, string keyByMetadataName) 73 { 74 var missing = partsWithMatchedDescriptors.Where(p => !p.Item2.Metadata.ContainsKey(keyByMetadataName)).ToArray(); 75 if (missing.Length != 0) 76 { 77 var problems = Formatters.ReadableQuotedList(missing.Select(p => p.Item1)); 78 var message = string.Format("The metadata '{0}' cannot be used as a dictionary import key because it is missing from exports on part(s) {1}.", keyByMetadataName, problems); 79 throw new CompositionFailedException(message); 80 } 81 82 var wrongType = partsWithMatchedDescriptors.Where(p => !(p.Item2.Metadata[keyByMetadataName] is TKey)).ToArray(); 83 if (wrongType.Length != 0) 84 { 85 var problems = Formatters.ReadableQuotedList(wrongType.Select(p => p.Item1)); 86 var message = string.Format("The metadata '{0}' cannot be used as a dictionary import key of type '{1}' because the value(s) supplied by {2} are of the wrong type.", keyByMetadataName, typeof(TKey).Name, problems); 87 throw new CompositionFailedException(message); 88 } 89 90 var firstDuplicated = partsWithMatchedDescriptors.GroupBy(p => (TKey)p.Item2.Metadata[keyByMetadataName]).Where(g => g.Count() > 1).FirstOrDefault(); 91 if (firstDuplicated != null) 92 { 93 var problems = Formatters.ReadableQuotedList(firstDuplicated.Select(p => p.Item1)); 94 var message = string.Format("The metadata '{0}' cannot be used as a dictionary import key because the value '{1}' is associated with exports from parts {2}.", keyByMetadataName, firstDuplicated.Key, problems); 95 throw new CompositionFailedException(message); 96 } 97 } 98 } 99 } 100