diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index fdc77d446165d8..1d0992781f08e6 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -554,9 +554,9 @@ private static bool CanBindToTheseConstructorParameters(ParameterInfo[] construc // Binds and potentially overwrites a concrete dictionary. // This differs from BindDictionaryInterface because this method doesn't clone // the dictionary; it sets and/or overwrites values directly. - // When a user specifies a concrete dictionary in their config class, then that - // value is used as-us. When a user specifies an interface (instantiated) in their config class, - // then it is cloned to a new dictionary, the same way as other collections. + // When a user specifies a concrete dictionary or a concrete class implementing IDictionary<,> + // in their config class, then that value is used as-is. When a user specifies an interface (instantiated) + // in their config class, then it is cloned to a new dictionary, the same way as other collections. [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode("Cannot statically analyze what the element type is of the value objects in the dictionary so its members may be trimmed.")] private static void BindConcreteDictionary( @@ -584,10 +584,17 @@ private static void BindConcreteDictionary( return; } - Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); + MethodInfo tryGetValue = dictionaryType.GetMethod("TryGetValue", BindingFlags.Public | BindingFlags.Instance)!; + + Debug.Assert(dictionary is not null); + // dictionary should be of type Dictionary<,> or of type implementing IDictionary<,> + PropertyInfo? setter = dictionary.GetType().GetProperty("Item", BindingFlags.Public | BindingFlags.Instance); + if (setter is null || !setter.CanWrite) + { + // Cannot set any item on the dictionary object. + return; + } - MethodInfo tryGetValue = dictionaryType.GetMethod("TryGetValue")!; - PropertyInfo setter = genericType.GetProperty("Item", DeclaredOnlyLookup)!; foreach (IConfigurationSection child in config.GetChildren()) { try diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationCollectionBindingTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationCollectionBindingTests.cs index 4f2b5911b2b7a9..cdf9448e9c7b0e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationCollectionBindingTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationCollectionBindingTests.cs @@ -1067,7 +1067,7 @@ public void CanBindInitializedIEnumerableAndTheOriginalItemsAreNotMutated() {"AlreadyInitializedIEnumerableInterface:1", "val1"}, {"AlreadyInitializedIEnumerableInterface:2", "val2"}, {"AlreadyInitializedIEnumerableInterface:x", "valx"}, - + {"ICollectionNoSetter:0", "val0"}, {"ICollectionNoSetter:1", "val1"}, }; @@ -1602,5 +1602,61 @@ private class OptionsWithInterdependentProperties public IEnumerable FilteredConfigValues => ConfigValues.Where(p => p > 10); public IEnumerable ConfigValues { get; set; } } + + [Fact] + public void DifferentDictionaryBindingCasesTest() + { + var dic = new Dictionary() { { "key", "value" } }; + var config = new ConfigurationBuilder() + .AddInMemoryCollection(dic) + .Build(); + + Assert.Single(config.Get>()); + Assert.Single(config.Get>()); + Assert.Single(config.Get>()); + Assert.Single(config.Get>()); + } + + public class ImplementerOfIDictionaryClass : IDictionary + { + private Dictionary _dict = new(); + + public TValue this[TKey key] { get => _dict[key]; set => _dict[key] = value; } + + public ICollection Keys => _dict.Keys; + + public ICollection Values => _dict.Values; + + public int Count => _dict.Count; + + public bool IsReadOnly => false; + + public void Add(TKey key, TValue value) => _dict.Add(key, value); + + public void Add(KeyValuePair item) => _dict.Add(item.Key, item.Value); + + public void Clear() => _dict.Clear(); + + public bool Contains(KeyValuePair item) => _dict.Contains(item); + + public bool ContainsKey(TKey key) => _dict.ContainsKey(key); + + public void CopyTo(KeyValuePair[] array, int arrayIndex) => throw new NotImplementedException(); + + public IEnumerator> GetEnumerator() => _dict.GetEnumerator(); + + public bool Remove(TKey key) => _dict.Remove(key); + + public bool Remove(KeyValuePair item) => _dict.Remove(item.Key); + + public bool TryGetValue(TKey key, out TValue value) => _dict.TryGetValue(key, out value); + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _dict.GetEnumerator(); + } + + public class ExtendedDictionary : Dictionary + { + + } } }