diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index 11ae61647d6ee8..f8ffe433bf413e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -250,7 +250,7 @@ private static void BindProperties(object instance, IConfiguration configuration continue; } - if (constructorParameters is null || !constructorParameters.Any(p => p.Name == property.Name)) + if (constructorParameters is null || !constructorParameters.Any(p => string.Equals(p.Name, property.Name, StringComparison.OrdinalIgnoreCase))) { BindProperty(property, instance, configuration, options); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Helpers.cs index 0d8976bb00de5a..f8b5d9282fa0a0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Helpers.cs @@ -21,6 +21,8 @@ public const bool NotSourceGenMode = true; #endif + public const bool SourceGenMode = !NotSourceGenMode; + public static IConfiguration GetConfigurationFromJsonString(string json) { var builder = new ConfigurationBuilder(); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs index 488e3699423463..184eb5fa6cf297 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs @@ -221,6 +221,30 @@ public string Value public record RecordWithArrayParameter(string[] Array); + public class GetterOnlyCollectionWithCaseMismatchedCtorParameter + { + public GetterOnlyCollectionWithCaseMismatchedCtorParameter(List instances) => Instances = instances; + public List Instances { get; } + } + + public class SettableCollectionWithCaseMismatchedCtorParameter + { + public SettableCollectionWithCaseMismatchedCtorParameter(List instances) => Instances = instances; + public List Instances { get; set; } + } + + public class GetterOnlyInterfaceCollectionWithCaseMismatchedCtorParameter + { + public GetterOnlyInterfaceCollectionWithCaseMismatchedCtorParameter(IList instances) => Instances = instances; + public IList Instances { get; } + } + + public class ParamsCollectionCtor + { + public ParamsCollectionCtor(params List instances) => Instances = instances; + public List Instances { get; } + } + public readonly record struct ReadonlyRecordStructTypeOptions(string Color, int Length); public class ContainerWithNestedImmutableObject diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs index ca87c05443e850..0197d107afc384 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -1667,6 +1667,29 @@ public void CanBindOnParametersAndProperties_RecordWithArrayConstructorParameter Assert.Equal(new string[] { "a", "b", "c" }, options.Array); } + /// + /// When a constructor parameter name differs only by case from a matching collection property, + /// the binder must bind the collection once (through the constructor) and must not bind it again + /// through the property, which would otherwise duplicate the collection items. + /// + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/83803", typeof(TestHelpers), nameof(TestHelpers.SourceGenMode))] + public void CanBindOnParametersAndProperties_GetterOnlyCollectionWithCaseMismatchedConstructorParameter() + { + string json = """ + { + "Instances": [ "first", "second" ] + } + """; + + IConfiguration config = TestHelpers.GetConfigurationFromJsonString(json); + string[] expected = new[] { "first", "second" }; + + Assert.Equal(expected, config.Get().Instances); + Assert.Equal(expected, config.Get().Instances); + Assert.Equal(expected, config.Get().Instances); + Assert.Equal(expected, config.Get().Instances); + } public static IEnumerable Configuration_TestData() {