Skip to content

Commit e6700ea

Browse files
authored
Fix Binding with IDictionary<,> implementer types (#77582)
1 parent 6893509 commit e6700ea

File tree

2 files changed

+70
-7
lines changed

2 files changed

+70
-7
lines changed

src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -554,9 +554,9 @@ private static bool CanBindToTheseConstructorParameters(ParameterInfo[] construc
554554
// Binds and potentially overwrites a concrete dictionary.
555555
// This differs from BindDictionaryInterface because this method doesn't clone
556556
// the dictionary; it sets and/or overwrites values directly.
557-
// When a user specifies a concrete dictionary in their config class, then that
558-
// value is used as-us. When a user specifies an interface (instantiated) in their config class,
559-
// then it is cloned to a new dictionary, the same way as other collections.
557+
// When a user specifies a concrete dictionary or a concrete class implementing IDictionary<,>
558+
// in their config class, then that value is used as-is. When a user specifies an interface (instantiated)
559+
// in their config class, then it is cloned to a new dictionary, the same way as other collections.
560560
[RequiresDynamicCode(DynamicCodeWarningMessage)]
561561
[RequiresUnreferencedCode("Cannot statically analyze what the element type is of the value objects in the dictionary so its members may be trimmed.")]
562562
private static void BindConcreteDictionary(
@@ -584,10 +584,17 @@ private static void BindConcreteDictionary(
584584
return;
585585
}
586586

587-
Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
587+
MethodInfo tryGetValue = dictionaryType.GetMethod("TryGetValue", BindingFlags.Public | BindingFlags.Instance)!;
588+
589+
Debug.Assert(dictionary is not null);
590+
// dictionary should be of type Dictionary<,> or of type implementing IDictionary<,>
591+
PropertyInfo? setter = dictionary.GetType().GetProperty("Item", BindingFlags.Public | BindingFlags.Instance);
592+
if (setter is null || !setter.CanWrite)
593+
{
594+
// Cannot set any item on the dictionary object.
595+
return;
596+
}
588597

589-
MethodInfo tryGetValue = dictionaryType.GetMethod("TryGetValue")!;
590-
PropertyInfo setter = genericType.GetProperty("Item", DeclaredOnlyLookup)!;
591598
foreach (IConfigurationSection child in config.GetChildren())
592599
{
593600
try

src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationCollectionBindingTests.cs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,7 @@ public void CanBindInitializedIEnumerableAndTheOriginalItemsAreNotMutated()
10671067
{"AlreadyInitializedIEnumerableInterface:1", "val1"},
10681068
{"AlreadyInitializedIEnumerableInterface:2", "val2"},
10691069
{"AlreadyInitializedIEnumerableInterface:x", "valx"},
1070-
1070+
10711071
{"ICollectionNoSetter:0", "val0"},
10721072
{"ICollectionNoSetter:1", "val1"},
10731073
};
@@ -1602,5 +1602,61 @@ private class OptionsWithInterdependentProperties
16021602
public IEnumerable<int> FilteredConfigValues => ConfigValues.Where(p => p > 10);
16031603
public IEnumerable<int> ConfigValues { get; set; }
16041604
}
1605+
1606+
[Fact]
1607+
public void DifferentDictionaryBindingCasesTest()
1608+
{
1609+
var dic = new Dictionary<string, string>() { { "key", "value" } };
1610+
var config = new ConfigurationBuilder()
1611+
.AddInMemoryCollection(dic)
1612+
.Build();
1613+
1614+
Assert.Single(config.Get<Dictionary<string, string>>());
1615+
Assert.Single(config.Get<IDictionary<string, string>>());
1616+
Assert.Single(config.Get<ExtendedDictionary<string, string>>());
1617+
Assert.Single(config.Get<ImplementerOfIDictionaryClass<string, string>>());
1618+
}
1619+
1620+
public class ImplementerOfIDictionaryClass<TKey, TValue> : IDictionary<TKey, TValue>
1621+
{
1622+
private Dictionary<TKey, TValue> _dict = new();
1623+
1624+
public TValue this[TKey key] { get => _dict[key]; set => _dict[key] = value; }
1625+
1626+
public ICollection<TKey> Keys => _dict.Keys;
1627+
1628+
public ICollection<TValue> Values => _dict.Values;
1629+
1630+
public int Count => _dict.Count;
1631+
1632+
public bool IsReadOnly => false;
1633+
1634+
public void Add(TKey key, TValue value) => _dict.Add(key, value);
1635+
1636+
public void Add(KeyValuePair<TKey, TValue> item) => _dict.Add(item.Key, item.Value);
1637+
1638+
public void Clear() => _dict.Clear();
1639+
1640+
public bool Contains(KeyValuePair<TKey, TValue> item) => _dict.Contains(item);
1641+
1642+
public bool ContainsKey(TKey key) => _dict.ContainsKey(key);
1643+
1644+
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => throw new NotImplementedException();
1645+
1646+
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => _dict.GetEnumerator();
1647+
1648+
public bool Remove(TKey key) => _dict.Remove(key);
1649+
1650+
public bool Remove(KeyValuePair<TKey, TValue> item) => _dict.Remove(item.Key);
1651+
1652+
public bool TryGetValue(TKey key, out TValue value) => _dict.TryGetValue(key, out value);
1653+
1654+
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _dict.GetEnumerator();
1655+
}
1656+
1657+
public class ExtendedDictionary<TKey, TValue> : Dictionary<TKey, TValue>
1658+
{
1659+
1660+
}
16051661
}
16061662
}

0 commit comments

Comments
 (0)