Skip to content

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

Merged
tarekgh merged 3 commits into
dotnet:mainfrom
tarekgh:fix-configbinder-ctor-param-case
Jun 26, 2026
Merged

Fix duplicated collection items when a constructor parameter name differs only by case from the property#129775
tarekgh merged 3 commits into
dotnet:mainfrom
tarekgh:fix-configbinder-ctor-param-case

Conversation

@tarekgh

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.

CopilotAI 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-basedMicrosoft.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.

FileDescription
src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.csMakes the constructor-parameter coverage check case-insensitive to prevent rebinding and collection duplication.
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.csAdds 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.csAdds test-only types exercising the constructor/property casing scenario across collection variants.

@rosebyte
rosebyte requested a review from svickJune 25, 2026 08:47
@tarekgh

Copy link
Copy Markdown
MemberAuthor

@rosebyte or @svick could you please re-approve as I pushed feedback suggestion?

CopilotAI review requested due to automatic review settings June 26, 2026 01:16

CopilotAI 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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

@tarekgh
tarekgh merged commit 4d1dc2a into dotnet:mainJun 26, 2026
85 of 88 checks passed
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…fers only by case from the property (#129775)
## 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:
```csharp
!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:
```csharp
!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.
---------
Co-authored-by: Tarek Mahmoud Sayed <tarekms@ntdev.microsoft.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 29, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

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

@tarekgh@svick@rosebyte