Skip to content

Fix duplicated collection items when a constructor parameter name differs only by case from the property#129775

Open
tarekgh wants to merge 1 commit into
dotnet:mainfrom
tarekgh:fix-configbinder-ctor-param-case
Open

Fix duplicated collection items when a constructor parameter name differs only by case from the property#129775
tarekgh wants to merge 1 commit into
dotnet:mainfrom
tarekgh:fix-configbinder-ctor-param-case

Conversation

@tarekgh

@tarekgh tarekgh commented Jun 23, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #129760.

When binding configuration to a type that is created through a constructor whose parameter populates a collection property, the collection items were duplicated if the constructor parameter name differed only by case from the property name (for example a parameter instances and a property Instances).

Root cause

Binding a type with a parameterized constructor happens in two phases:

  1. The constructor is invoked, binding the configuration section to each parameter, which populates the matching property.
  2. BindProperties iterates the properties and binds any property that is not already covered by a constructor parameter.

The check in phase 2 used a case-sensitive comparison to decide whether a constructor parameter already covered a property:

!constructorParameters.Any(p => p.Name == property.Name)

The rest of the binder matches constructor parameters and properties case-insensitively (DoAllParametersHaveEquivalentProperties uses StringComparer.OrdinalIgnoreCase), and configuration keys are case-insensitive as well. Because of the case-sensitive check, a parameter instances was not recognized as covering the property Instances, so the property was bound a second time and the items were appended to the collection that the constructor had already populated, producing duplicates. Records were not affected because the compiler generated property keeps the exact parameter casing.

Fix

Align the comparison with the case-insensitive matching used everywhere else in the binder so a property is not re-bound when a constructor parameter already covers it:

!constructorParameters.Any(p => string.Equals(p.Name, property.Name, StringComparison.OrdinalIgnoreCase))

Scope

This change addresses the reflection based runtime binder only. The configuration source generator has the same problem and is tracked by #83803. The source generator case, including a class (not a record) using a params constructor, should be addressed when fixing that tracking issue so both paths behave the same.

Tests

Added a regression test covering getter only collection, settable collection, getter only interface collection, and a params collection constructor, all with a constructor parameter name that differs only by case from the property. The test is gated to the reflection binder (NotSourceGenMode) because the source generator fix is tracked separately. Verified the test fails without the fix (items duplicated) and passes with it, and that all existing binder tests continue to pass.

…rs only by case from the property

The reflection binder decided whether to re-bind a property already bound through a constructor parameter using a case-sensitive name comparison, while the rest of the binder matches parameters and properties case-insensitively. When a constructor parameter such as 'instances' populated a collection property such as 'Instances', the property was bound a second time and the collection items were duplicated.

Align the comparison with the case-insensitive matching used elsewhere so the property is not re-bound when a constructor parameter already covers it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug in the reflection-based Microsoft.Extensions.Configuration.Binder where collection values could be bound twice (and therefore duplicated) when a constructor parameter name and its corresponding property name differ only by case.

Changes:

  • Update BindProperties to treat constructor parameters and properties as matching case-insensitively, consistent with the rest of the binder and configuration key semantics.
  • Add a regression test covering multiple collection shapes (getter-only, settable, interface-typed, and params collection constructor) to ensure items are not duplicated.
  • Introduce minimal test helper types used by the regression test.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs Makes the constructor-parameter coverage check case-insensitive to prevent rebinding and collection duplication.
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs Adds a regression test (reflection-only) validating collections aren’t duplicated when ctor parameter casing differs from the property.
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs Adds test-only types exercising the constructor/property casing scenario across collection variants.

@rosebyte rosebyte requested a review from svick June 25, 2026 08:47
/// 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.
/// </summary>
[ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))]

@svick svick Jun 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could use [ActiveIssue]:

Suggested change
[ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))]
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/83803", typeof(TestHelpers), nameof(TestHelpers.SourceGenMode))]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate List<T> items when binding class with default constructor containing params keyword to IConfiguration

4 participants