Skip to content

Commit 30426a7

Browse files
[release/7.0] Fix configuration binding with types implementing IDictionary<,> (#79019)
* Fix configuration binding with types implementing IDictionary<,> * OOB package authoring changes Co-authored-by: Carlos Sanchez <[email protected]>
1 parent e721ae9 commit 30426a7

File tree

3 files changed

+22
-14
lines changed

3 files changed

+22
-14
lines changed

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ private static void BindInstance(
367367

368368
if (dictionaryInterface != null)
369369
{
370-
BindConcreteDictionary(bindingPoint.Value!, dictionaryInterface, config, options);
370+
BindDictionary(bindingPoint.Value!, dictionaryInterface, config, options);
371371
}
372372
else
373373
{
@@ -549,25 +549,28 @@ private static bool CanBindToTheseConstructorParameters(ParameterInfo[] construc
549549
}
550550
}
551551

552-
BindConcreteDictionary(dictionary, dictionaryType, config, options);
552+
BindDictionary(dictionary, genericType, config, options);
553553

554554
return dictionary;
555555
}
556556

557-
// Binds and potentially overwrites a concrete dictionary.
557+
// Binds and potentially overwrites a dictionary object.
558558
// This differs from BindDictionaryInterface because this method doesn't clone
559559
// the dictionary; it sets and/or overwrites values directly.
560560
// When a user specifies a concrete dictionary or a concrete class implementing IDictionary<,>
561561
// in their config class, then that value is used as-is. When a user specifies an interface (instantiated)
562562
// in their config class, then it is cloned to a new dictionary, the same way as other collections.
563563
[RequiresDynamicCode(DynamicCodeWarningMessage)]
564564
[RequiresUnreferencedCode("Cannot statically analyze what the element type is of the value objects in the dictionary so its members may be trimmed.")]
565-
private static void BindConcreteDictionary(
566-
object? dictionary,
565+
private static void BindDictionary(
566+
object dictionary,
567567
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
568568
Type dictionaryType,
569569
IConfiguration config, BinderOptions options)
570570
{
571+
Debug.Assert(dictionaryType.IsGenericType &&
572+
(dictionaryType.GetGenericTypeDefinition() == typeof(IDictionary<,>) || dictionaryType.GetGenericTypeDefinition() == typeof(Dictionary<,>)));
573+
571574
Type keyType = dictionaryType.GenericTypeArguments[0];
572575
Type valueType = dictionaryType.GenericTypeArguments[1];
573576
bool keyTypeIsEnum = keyType.IsEnum;
@@ -589,13 +592,10 @@ private static void BindConcreteDictionary(
589592

590593
Debug.Assert(dictionary is not null);
591594

592-
Type dictionaryObjectType = dictionary.GetType();
593-
594-
MethodInfo tryGetValue = dictionaryObjectType.GetMethod("TryGetValue", BindingFlags.Public | BindingFlags.Instance)!;
595+
MethodInfo tryGetValue = dictionaryType.GetMethod("TryGetValue", DeclaredOnlyLookup)!;
596+
PropertyInfo? indexerProperty = dictionaryType.GetProperty("Item", DeclaredOnlyLookup);
595597

596-
// dictionary should be of type Dictionary<,> or of type implementing IDictionary<,>
597-
PropertyInfo? setter = dictionaryObjectType.GetProperty("Item", BindingFlags.Public | BindingFlags.Instance);
598-
if (setter is null || !setter.CanWrite)
598+
if (indexerProperty is null || !indexerProperty.CanWrite)
599599
{
600600
// Cannot set any item on the dictionary object.
601601
return;
@@ -623,7 +623,7 @@ private static void BindConcreteDictionary(
623623
options: options);
624624
if (valueBindingPoint.HasNewValue)
625625
{
626-
setter.SetValue(dictionary, valueBindingPoint.Value, new object[] { key });
626+
indexerProperty.SetValue(dictionary, valueBindingPoint.Value, new object[] { key });
627627
}
628628
}
629629
catch

src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
<EnableDefaultItems>true</EnableDefaultItems>
66
<IsPackable>true</IsPackable>
77
<EnableAOTAnalyzer>true</EnableAOTAnalyzer>
8-
<ServicingVersion>1</ServicingVersion>
8+
<ServicingVersion>2</ServicingVersion>
9+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
910
<PackageDescription>Functionality to bind an object to data in configuration providers for Microsoft.Extensions.Configuration.</PackageDescription>
1011
</PropertyGroup>
1112

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1171,7 +1171,7 @@ public void CanBindInitializedCustomIndirectlyDerivedIEnumerableList()
11711171
}
11721172

11731173
[Fact]
1174-
public void CanBindInitializedIReadOnlyDictionaryAndDoesNotMofifyTheOriginal()
1174+
public void CanBindInitializedIReadOnlyDictionaryAndDoesNotModifyTheOriginal()
11751175
{
11761176
// A field declared as IEnumerable<T> that is instantiated with a class
11771177
// that indirectly implements IEnumerable<T> is still bound, but with
@@ -1672,6 +1672,13 @@ public class ImplementerOfIDictionaryClass<TKey, TValue> : IDictionary<TKey, TVa
16721672
public bool TryGetValue(TKey key, out TValue value) => _dict.TryGetValue(key, out value);
16731673

16741674
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _dict.GetEnumerator();
1675+
1676+
// The following are members which have the same names as the IDictionary<,> members.
1677+
// The following members test that there's no System.Reflection.AmbiguousMatchException when binding to the dictionary.
1678+
private string? v;
1679+
public string? this[string key] { get => v; set => v = value; }
1680+
public bool TryGetValue() { return true; }
1681+
16751682
}
16761683

16771684
public class ExtendedDictionary<TKey, TValue> : Dictionary<TKey, TValue>

0 commit comments

Comments
 (0)