Skip to content

Commit ac10e7e

Browse files
Copilotgewarren
andcommitted
Add breaking change documentation for configuration null values preservation
Co-authored-by: gewarren <[email protected]>
1 parent 2a2aac6 commit ac10e7e

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed

docs/core/compatibility/10.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ If you're migrating an app to .NET 10, the breaking changes listed here might af
4141

4242
| Title | Type of change | Introduced version |
4343
|-------|---------------------|--------------------|
44+
| [Preserving null values in configuration](extensions/10.0/configuration-null-values-preserved.md) | Behavioral change | Preview 7 |
4445
| [ProviderAliasAttribute moved to Microsoft.Extensions.Logging.Abstractions assembly](extensions/10.0/provideraliasattribute-moved-assembly.md) | Source incompatible | Preview 4 |
4546
| [Removed DynamicallyAccessedMembers annotation from trim-unsafe Microsoft.Extensions.Configuration code](extensions/10.0/dynamically-accessed-members-configuration.md) | Binary incompatible | Preview 6 |
4647

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
title: "Breaking change: Preserving null values in configuration"
3+
description: "Learn about the breaking change in .NET 10 where configuration providers now preserve null values instead of treating them as missing values."
4+
ms.date: 12/17/2024
5+
ai-usage: ai-assisted
6+
ms.custom: https://github.com/dotnet/docs/issues/46890
7+
---
8+
9+
# Preserving null values in configuration
10+
11+
The .NET configuration binder now preserves null values in configuration instead of treating them as missing values. This change affects how the JSON configuration provider handles null values and how the configuration binder processes them during binding operations.
12+
13+
## Version introduced
14+
15+
.NET 10 Preview 7
16+
17+
## Previous behavior
18+
19+
Previously, when a configuration value was `null`, the binder treated it as if the value didn't exist at all, and therefore skipped the binding. The system didn't distinguish between `null` values and missing values.
20+
21+
Additionally, the JSON configuration provider converted `null` values in the configuration to empty strings. This caused properties bound to these values to receive an empty string rather than the expected `null`.
22+
23+
Consider the following configuration file `appsettings.json`:
24+
25+
```json
26+
{
27+
"NullConfiguration": {
28+
"StringProperty": null,
29+
"IntProperty": null,
30+
"Array1": [null, null],
31+
"Array2": []
32+
}
33+
}
34+
```
35+
36+
And the corresponding binding code:
37+
38+
```csharp
39+
public class NullConfiguration
40+
{
41+
public NullConfiguration()
42+
{
43+
// Initialize with non-default value to ensure binding will override these values
44+
StringProperty = "Initial Value";
45+
IntProperty = 123;
46+
}
47+
public string? StringProperty { get; set; }
48+
public int? IntProperty { get; set; }
49+
public string[]? Array1 { get; set; }
50+
public string[]? Array2 { get; set; }
51+
}
52+
53+
var configuration = new ConfigurationBuilder()
54+
.AddJsonFile("appsettings.json")
55+
.Build().GetSection("NullConfiguration");
56+
57+
// Now bind the configuration
58+
NullConfiguration? result = configuration.Get<NullConfiguration>();
59+
60+
Console.WriteLine($"StringProperty: '{result!.StringProperty}', intProperty: {(result!.IntProperty.HasValue ? result!.IntProperty : "null")}");
61+
Console.WriteLine($"Array1: {(result!.Array1 is null ? "null" : string.Join(", ", result!.Array1.Select(a => $"'{(a is null ? "null" : a)}'")))}");
62+
Console.WriteLine($"Array2: {(result!.Array2 is null ? "null" : string.Join(", ", result!.Array2.Select(a => $"'{(a is null ? "null" : a)}'")))}");
63+
```
64+
65+
Output:
66+
67+
```
68+
StringProperty: '', intProperty: 123
69+
Array1: '', ''
70+
Array2: null
71+
```
72+
73+
Explanation:
74+
- `StringProperty`: The null value in the JSON was converted by the JSON provider into an empty string (""), overwriting the initial value.
75+
- `IntProperty`: Remained unchanged (123) because the provider converted null to an empty string, which couldn't be parsed as an `int?`, so the original value was retained.
76+
- `Array1`: Bound to an array containing two empty strings because each null array element was treated as an empty string.
77+
- `Array2`: Remained null since an empty array `[]` in the JSON was ignored by the binder.
78+
79+
## New behavior
80+
81+
Null values in the configuration are now correctly honored. Running the same code sample produces the following results:
82+
83+
Using the JSON configuration provider:
84+
85+
```
86+
StringProperty: 'null', intProperty: null
87+
Array1: 'null', 'null'
88+
Array2:
89+
```
90+
91+
Null values are now properly bound to their corresponding properties, including array elements. Even empty arrays are correctly recognized and bound as empty arrays rather than being ignored.
92+
93+
## Type of breaking change
94+
95+
This is a [behavioral change](../../categories.md#behavioral-change).
96+
97+
## Reason for change
98+
99+
The previous behavior was confusing and frequently led to user complaints. By addressing this issue, the configuration binding process is now more intuitive and consistent, reducing confusion and aligning the behavior with user expectations.
100+
101+
## Recommended action
102+
103+
If you prefer the previous behavior, you can adjust your configuration accordingly:
104+
105+
- When using the **JSON configuration provider**, replace `null` values with empty strings (`""`) to restore the original behavior, where empty strings are bound instead of `null`.
106+
- For other providers that support `null` values, simply **remove the `null` entries** from the configuration to replicate the earlier behavior, where missing values are ignored and existing property values remain unchanged.
107+
108+
## Affected APIs
109+
110+
- <xref:Microsoft.Extensions.Configuration.ConfigurationBinder.Get``1(Microsoft.Extensions.Configuration.IConfiguration)?displayProperty=fullName>
111+
- <xref:Microsoft.Extensions.Configuration.ConfigurationBinder.Get``1(Microsoft.Extensions.Configuration.IConfiguration,System.Action{Microsoft.Extensions.Configuration.BinderOptions})?displayProperty=fullName>
112+
- <xref:Microsoft.Extensions.Configuration.ConfigurationBinder.Get(Microsoft.Extensions.Configuration.IConfiguration,System.Type)?displayProperty=fullName>
113+
- <xref:Microsoft.Extensions.Configuration.ConfigurationBinder.Get(Microsoft.Extensions.Configuration.IConfiguration,System.Type,System.Action{Microsoft.Extensions.Configuration.BinderOptions})?displayProperty=fullName>
114+
- <xref:Microsoft.Extensions.Configuration.ConfigurationBinder.Bind(Microsoft.Extensions.Configuration.IConfiguration,System.Object)?displayProperty=fullName>
115+
- <xref:Microsoft.Extensions.Configuration.ConfigurationBinder.Bind(Microsoft.Extensions.Configuration.IConfiguration,System.Object,System.Action{Microsoft.Extensions.Configuration.BinderOptions})?displayProperty=fullName>

docs/core/compatibility/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ items:
5050
href: cryptography/10.0/x509-publickey-null.md
5151
- name: Extensions
5252
items:
53+
- name: "Preserving null values in configuration"
54+
href: extensions/10.0/configuration-null-values-preserved.md
5355
- name: "ProviderAliasAttribute moved to Microsoft.Extensions.Logging.Abstractions assembly"
5456
href: extensions/10.0/provideraliasattribute-moved-assembly.md
5557
- name: "Removed DynamicallyAccessedMembers annotation from trim-unsafe Microsoft.Extensions.Configuration code"

0 commit comments

Comments
 (0)