-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Description
Description
Starting in .NET 8, ConfigurationBinder.Get<T>() / ConfigurationBinder.Bind() silently skips array or list elements whose values fail type conversion, resulting in a shorter collection. Previously in .NET 6/7, failed elements were preserved as null placeholders, maintaining the original array length.
This behavioral change affects any code that relies on array indices aligning with configuration indices, or that checks for null entries to detect binding failures.
Related runtime issue: dotnet/runtime#124169
The root cause is that the internal binding logic changed from pre-allocating the array and binding in-place (leaving null on failure) to first collecting successfully-bound elements into a list and then materializing the final array, which drops failed entries entirely.
Previous behavior
When binding an array/list property via IConfiguration.Get<T>() (with default BinderOptions), if an element contained a value that could not be converted to the target type (e.g., "a" for an int property), the element was preserved as a null placeholder in the resulting array. The array length matched the number of elements in configuration.
// appsettings.json
// "List": [
// { "Name": "A", "Interval": 10 },
// { "Name": "B", "Interval": "a" } <-- invalid int
// ]
var settings = configuration.Get<SampleSettings>();
// .NET 6/7 result:
// settings.List.Length == 2
// settings.List[0] = { Name = "A", Interval = 10 }
// settings.List[1] = null (conversion failed, but placeholder preserved)New behavior
Starting in .NET 8, elements that fail type conversion are silently skipped. The resulting array only contains successfully-bound elements, and its length is shorter than the number of configuration entries.
var settings = configuration.Get<SampleSettings>();
// .NET 8+ result:
// settings.List.Length == 1
// settings.List[0] = { Name = "A", Interval = 10 }
// List[1] is gone entirely — no null placeholderThis behavior persists in .NET 9 and .NET 10 as well.
Type of breaking change
- Behavioral change: Existing binaries might behave differently at run time.
Reason for change
The internal implementation of ConfigurationBinder was refactored in .NET 8. The binder changed from pre-allocating the target array and binding elements in-place (which left null on conversion failure) to collecting only successfully-bound elements into a temporary list before materializing the final array. This causes elements with conversion errors to be dropped instead of preserved. The previous behavior was also incorrect when binding to arrays of value types, such as int[]. For example, it could store 0 when encountering an invalid configuration value, and it would also store 0 when the actual configuration value was 0. This made it confusing and difficult to distinguish between a legitimate value and an error.
Recommended action
-
Enable
ErrorOnUnknownConfigurationduring development to catch invalid configuration values early:var settings = configuration.Get<SampleSettings>(options => options.ErrorOnUnknownConfiguration = true);
This will throw an
InvalidOperationExceptionwhen a value cannot be converted, making misconfiguration immediately visible rather than silently dropping elements. It is strongly recommended to enable this option at least during development and testing. -
Validate array lengths after binding if your code depends on the number of elements matching the configuration source.
-
Fix invalid configuration values — ensure all values in your configuration files match the expected types (e.g., don't use
"a"where anintis expected). -
Use string properties with manual parsing if you need to gracefully handle unconvertible values and preserve all array entries.
Feature area
Extensions
Affected APIs
Microsoft.Extensions.Configuration.ConfigurationBinder.Get<T>(IConfiguration)Microsoft.Extensions.Configuration.ConfigurationBinder.Get<T>(IConfiguration, Action<BinderOptions>)Microsoft.Extensions.Configuration.ConfigurationBinder.Bind(IConfiguration, object)Microsoft.Extensions.Configuration.ConfigurationBinder.Bind(IConfiguration, object, Action<BinderOptions>)