Skip to content

Commit 7f595ce

Browse files
authored
Allow ConfigBinder to bind arrays to Singular elements (#57204)
1 parent d952d8d commit 7f595ce

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

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

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public static class ConfigurationBinder
1919
private const string TrimmingWarningMessage = "In case the type is non-primitive, the trimmer cannot statically analyze the object's type so its members may be trimmed.";
2020
private const string InstanceGetTypeTrimmingWarningMessage = "Cannot statically analyze the type of instance so its members may be trimmed";
2121
private const string PropertyTrimmingWarningMessage = "Cannot statically analyze property.PropertyType so its members may be trimmed.";
22+
private const string BindSingleElementsToArraySwitch = "Microsoft.Extensions.Configuration.BindSingleElementsToArray";
2223

2324
/// <summary>
2425
/// Attempts to bind the configuration instance to a new instance of type T.
@@ -362,7 +363,7 @@ private static object BindInstance(
362363
return convertedValue;
363364
}
364365

365-
if (config != null && config.GetChildren().Any())
366+
if (config != null && (config.GetChildren().Any() || (configValue != null && ShouldBindSingleElementsToArray())))
366367
{
367368
// If we don't have an instance, try to create one
368369
if (instance == null)
@@ -495,7 +496,7 @@ private static void BindCollection(
495496
Type itemType = collectionType.GenericTypeArguments[0];
496497
MethodInfo addMethod = collectionType.GetMethod("Add", DeclaredOnlyLookup);
497498

498-
foreach (IConfigurationSection section in config.GetChildren())
499+
foreach (IConfigurationSection section in GetChildrenOrSelf(config))
499500
{
500501
try
501502
{
@@ -518,7 +519,7 @@ private static void BindCollection(
518519
[RequiresUnreferencedCode("Cannot statically analyze what the element type is of the Array so its members may be trimmed.")]
519520
private static Array BindArray(Array source, IConfiguration config, BinderOptions options)
520521
{
521-
IConfigurationSection[] children = config.GetChildren().ToArray();
522+
IConfigurationSection[] children = GetChildrenOrSelf(config).ToArray();
522523
int arrayLength = source.Length;
523524
Type elementType = source.GetType().GetElementType();
524525
var newArray = Array.CreateInstance(elementType, arrayLength + children.Length);
@@ -702,5 +703,37 @@ private static string GetPropertyName(MemberInfo property)
702703

703704
return property.Name;
704705
}
706+
707+
private static IEnumerable<IConfigurationSection> GetChildrenOrSelf(IConfiguration config)
708+
{
709+
if (!ShouldBindSingleElementsToArray())
710+
{
711+
return config.GetChildren();
712+
}
713+
714+
IEnumerable<IConfigurationSection> children;
715+
// If configuration's children is an array, the configuration key will be a number
716+
if (config.GetChildren().Any(a => long.TryParse(a.Key, out _)))
717+
{
718+
children = config.GetChildren();
719+
}
720+
else
721+
{
722+
children = new[] { config as IConfigurationSection };
723+
}
724+
725+
return children;
726+
}
727+
728+
private static bool ShouldBindSingleElementsToArray()
729+
{
730+
if (AppContext.TryGetSwitch(BindSingleElementsToArraySwitch, out bool bindSingleElementsToArray))
731+
{
732+
return bindSingleElementsToArray;
733+
}
734+
735+
// Enable this switch by default.
736+
return true;
737+
}
705738
}
706-
}
739+
}

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,58 @@ public void ExceptionWhenTryingToBindToByteArray()
936936
exception.Message);
937937
}
938938

939+
[Fact]
940+
public void CanBindSingleElementToCollection()
941+
{
942+
var dic = new Dictionary<string, string>
943+
{
944+
{"MyString", "hello world"},
945+
{"Nested:Integer", "11"},
946+
};
947+
948+
var configurationBuilder = new ConfigurationBuilder();
949+
configurationBuilder.AddInMemoryCollection(dic);
950+
IConfiguration config = configurationBuilder.Build();
951+
952+
var stringArr = config.GetSection("MyString").Get<string[]>();
953+
Assert.Equal("hello world", stringArr[0]);
954+
Assert.Equal(1, stringArr.Length);
955+
956+
var stringAsStr = config.GetSection("MyString").Get<string>();
957+
Assert.Equal("hello world", stringAsStr);
958+
959+
var nested = config.GetSection("Nested").Get<NestedOptions>();
960+
Assert.Equal(11, nested.Integer);
961+
962+
var nestedAsArray = config.GetSection("Nested").Get<NestedOptions[]>();
963+
Assert.Equal(11, nestedAsArray[0].Integer);
964+
Assert.Equal(1, nestedAsArray.Length);
965+
}
966+
967+
[Fact]
968+
public void CannotBindSingleElementToCollectionWhenSwitchSet()
969+
{
970+
AppContext.SetSwitch("Microsoft.Extensions.Configuration.BindSingleElementsToArray", false);
971+
972+
var dic = new Dictionary<string, string>
973+
{
974+
{"MyString", "hello world"},
975+
{"Nested:Integer", "11"},
976+
};
977+
978+
var configurationBuilder = new ConfigurationBuilder();
979+
configurationBuilder.AddInMemoryCollection(dic);
980+
IConfiguration config = configurationBuilder.Build();
981+
982+
var stringArr = config.GetSection("MyString").Get<string[]>();
983+
Assert.Null(stringArr);
984+
985+
var stringAsStr = config.GetSection("MyString").Get<string>();
986+
Assert.Equal("hello world", stringAsStr);
987+
988+
AppContext.SetSwitch("Microsoft.Extensions.Configuration.BindSingleElementsToArray", true);
989+
}
990+
939991
private interface ISomeInterface
940992
{
941993
}

0 commit comments

Comments
 (0)