From c8159b318e919854276c37ee102095a91f60e6c5 Mon Sep 17 00:00:00 2001 From: Maryam Ariyan Date: Wed, 25 Aug 2021 14:05:16 -0400 Subject: [PATCH] Allow configuration binder to bind single elements to array (#58060) * Allow ConfigBinder to bind arrays to Singular elements (#57204) * Apply feedback from PR #57204 (#57872) Co-authored-by: vidommet <80355385+vidommet@users.noreply.github.com> --- .../src/ConfigurationBinder.cs | 33 +++++++++++-- .../tests/ConfigurationBinderTests.cs | 54 ++++++++++++++++++++++ ...ft.Extensions.Configuration.Binder.Tests.csproj | 1 + 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index f13edb1..9e6a7d3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -19,6 +19,10 @@ namespace Microsoft.Extensions.Configuration 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."; private const string InstanceGetTypeTrimmingWarningMessage = "Cannot statically analyze the type of instance so its members may be trimmed"; private const string PropertyTrimmingWarningMessage = "Cannot statically analyze property.PropertyType so its members may be trimmed."; + private const string BindSingleElementsToArraySwitch = "Microsoft.Extensions.Configuration.BindSingleElementsToArray"; + + // Enable this switch by default. + private static bool ShouldBindSingleElementsToArray { get; } = AppContext.TryGetSwitch(BindSingleElementsToArraySwitch, out bool verifyCanBindSingleElementsToArray) ? verifyCanBindSingleElementsToArray : true; /// /// Attempts to bind the configuration instance to a new instance of type T. @@ -362,7 +366,7 @@ namespace Microsoft.Extensions.Configuration return convertedValue; } - if (config != null && config.GetChildren().Any()) + if (config != null && (config.GetChildren().Any() || (configValue != null && ShouldBindSingleElementsToArray))) { // If we don't have an instance, try to create one if (instance == null) @@ -495,7 +499,7 @@ namespace Microsoft.Extensions.Configuration Type itemType = collectionType.GenericTypeArguments[0]; MethodInfo addMethod = collectionType.GetMethod("Add", DeclaredOnlyLookup); - foreach (IConfigurationSection section in config.GetChildren()) + foreach (IConfigurationSection section in GetChildrenOrSelf(config)) { try { @@ -518,7 +522,7 @@ namespace Microsoft.Extensions.Configuration [RequiresUnreferencedCode("Cannot statically analyze what the element type is of the Array so its members may be trimmed.")] private static Array BindArray(Array source, IConfiguration config, BinderOptions options) { - IConfigurationSection[] children = config.GetChildren().ToArray(); + IConfigurationSection[] children = GetChildrenOrSelf(config).ToArray(); int arrayLength = source.Length; Type elementType = source.GetType().GetElementType(); var newArray = Array.CreateInstance(elementType, arrayLength + children.Length); @@ -702,5 +706,26 @@ namespace Microsoft.Extensions.Configuration return property.Name; } + + private static IEnumerable GetChildrenOrSelf(IConfiguration config) + { + if (!ShouldBindSingleElementsToArray) + { + return config.GetChildren(); + } + + IEnumerable children; + // If configuration's children is an array, the configuration key will be a number + if (config.GetChildren().Any(a => long.TryParse(a.Key, out _))) + { + children = config.GetChildren(); + } + else + { + children = new[] { config as IConfigurationSection }; + } + + return children; + } } -} +} \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs index c5dbd9d..1c9e891 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Reflection; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace Microsoft.Extensions.Configuration.Binder.Test @@ -936,6 +937,59 @@ namespace Microsoft.Extensions.Configuration.Binder.Test exception.Message); } + [Fact] + public void CanBindSingleElementToCollection() + { + var dic = new Dictionary + { + {"MyString", "hello world"}, + {"Nested:Integer", "11"}, + }; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + IConfiguration config = configurationBuilder.Build(); + + var stringArr = config.GetSection("MyString").Get(); + Assert.Equal("hello world", stringArr[0]); + Assert.Equal(1, stringArr.Length); + + var stringAsStr = config.GetSection("MyString").Get(); + Assert.Equal("hello world", stringAsStr); + + var nested = config.GetSection("Nested").Get(); + Assert.Equal(11, nested.Integer); + + var nestedAsArray = config.GetSection("Nested").Get(); + Assert.Equal(11, nestedAsArray[0].Integer); + Assert.Equal(1, nestedAsArray.Length); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void CannotBindSingleElementToCollectionWhenSwitchSet() + { + RemoteExecutor.Invoke(() => + { + AppContext.SetSwitch("Microsoft.Extensions.Configuration.BindSingleElementsToArray", false); + + var dic = new Dictionary + { + {"MyString", "hello world"}, + {"Nested:Integer", "11"}, + }; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + IConfiguration config = configurationBuilder.Build(); + + var stringArr = config.GetSection("MyString").Get(); + Assert.Null(stringArr); + + var stringAsStr = config.GetSection("MyString").Get(); + Assert.Equal("hello world", stringAsStr); + }).Dispose(); + } + private interface ISomeInterface { } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Microsoft.Extensions.Configuration.Binder.Tests.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Microsoft.Extensions.Configuration.Binder.Tests.csproj index d2323d0..754d58e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Microsoft.Extensions.Configuration.Binder.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Microsoft.Extensions.Configuration.Binder.Tests.csproj @@ -3,6 +3,7 @@ $(NetCoreAppCurrent);net461 true + true -- 2.7.4