Skip to content

Commit ddf76f5

Browse files
authored
Document advanced configuration binding scenarios and limitations (#51541)
1 parent 2fb23a5 commit ddf76f5

4 files changed

Lines changed: 191 additions & 23 deletions

File tree

docs/core/compatibility/extensions/7.0/config-bind-dictionary.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ ms.date: 08/02/2023
55
---
66
# Binding config to dictionary extends values
77

8-
When binding a configuration using a <xref:System.Collections.Generic.Dictionary%602> object where the value is a mutable collection type, binding to the same key more than once now extends the values collection instead of replacing the whole collection with the new value.
8+
When binding a configuration using a <xref:System.Collections.Generic.Dictionary`2> object where the value is a mutable collection type, binding to the same key more than once now extends the values collection instead of replacing the whole collection with the new value.
99

1010
## Version introduced
1111

docs/core/extensions/configuration.md

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ title: Configuration
33
description: Learn how to use the Configuration API to configure .NET applications. Explore various inbuilt configuration providers.
44
ms.date: 10/09/2024
55
ms.topic: overview
6+
ai-usage: ai-assisted
67
---
78

89
# Configuration in .NET
@@ -14,7 +15,7 @@ Configuration in .NET is performed using one or more [configuration providers](#
1415
- [Azure Key Vault](/azure/key-vault/general/overview)
1516
- [Azure App Configuration](/azure/azure-app-configuration/overview)
1617
- Command-line arguments
17-
- Custom providers, installed or created
18+
- Custom providers (installed or created)
1819
- Directory files
1920
- In-memory .NET objects
2021
- Third-party providers
@@ -30,7 +31,7 @@ Given one or more configuration sources, the <xref:Microsoft.Extensions.Configur
3031

3132
## Configure console apps
3233

33-
.NET console applications created using the [dotnet new](../tools/dotnet-new.md) command template or Visual Studio by default *don't* expose configuration capabilities. To add configuration in a new .NET console application, [add a package reference](../tools/dotnet-package-add.md) to [Microsoft.Extensions.Configuration](https://www.nuget.org/packages/Microsoft.Extensions.Configuration). This package is the foundation for configuration in .NET apps. It provides the <xref:Microsoft.Extensions.Configuration.ConfigurationBuilder> and related types.
34+
.NET console apps created using the [dotnet new](../tools/dotnet-new.md) command template or Visual Studio by default *don't* expose configuration capabilities. To add configuration in a new .NET console application, [add a package reference](../tools/dotnet-package-add.md) to [📦 Microsoft.Extensions.Configuration](https://www.nuget.org/packages/Microsoft.Extensions.Configuration). This package is the foundation for configuration in .NET apps. It provides the <xref:Microsoft.Extensions.Configuration.ConfigurationBuilder> and related types.
3435

3536
:::code source="snippets/configuration/console-basic-builder/Program.cs":::
3637

@@ -41,11 +42,11 @@ The preceding code:
4142
- Calls the <xref:Microsoft.Extensions.Configuration.ConfigurationBuilder.Build> method to create an <xref:Microsoft.Extensions.Configuration.IConfiguration> instance.
4243
- Writes the value of the `SomeKey` key to the console.
4344

44-
While this example uses an in-memory configuration, there are many configuration providers available, exposing functionality for file-based, environment variables, command line arguments, and other configuration sources. For more information, see [Configuration providers in .NET](configuration-providers.md).
45+
While this example uses an in-memory configuration, there are many configuration providers available, exposing functionality for file-based, environment variables, command-line arguments, and other configuration sources. For more information, see [Configuration providers in .NET](configuration-providers.md).
4546

46-
### Alternative hosting approach
47+
## Alternative hosting approach
4748

48-
Commonly, your apps will do more than just read configuration. They'll likely use dependency injection, logging, and other services. The [.NET Generic Host](generic-host.md) approach is recommended for apps that use these services. Instead, consider [adding a package reference](../tools/dotnet-package-add.md) to [Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting). Modify the *Program.cs* file to match the following code:
49+
Commonly, your apps will do more than just read configuration. They'll likely use dependency injection, logging, and other services. The [.NET Generic Host](generic-host.md) approach is recommended for apps that use these services. Instead, consider [adding a package reference](../tools/dotnet-package-add.md) to [📦 Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting). Modify the *Program.cs* file to match the following code:
4950

5051
:::code source="snippets/configuration/console/Program.cs" highlight="3":::
5152

@@ -60,9 +61,9 @@ The <xref:Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder(System.Stri
6061

6162
Adding a configuration provider overrides previous configuration values. For example, the [Command-line configuration provider](configuration-providers.md#command-line-configuration-provider) overrides all values from other providers because it's added last. If `SomeKey` is set in both *appsettings.json* and the environment, the environment value is used because it was added after *appsettings.json*.
6263

63-
### Binding
64+
## Binding
6465

65-
One of the key advantages of using the .NET configuration abstractions is the ability to bind configuration values to instances of .NET objects. For example, the JSON configuration provider can be used to map *appsettings.json* files to .NET objects and is used with [dependency injection](dependency-injection/overview.md). This enables the [options pattern](options.md), which uses classes to provide strongly typed access to groups of related settings. The default binder is reflection-based, but there's a [source generator alternative](configuration-generator.md) that's easy to enable.
66+
One of the key advantages of using the .NET configuration abstractions is the ability to *bind* configuration values to instances of .NET objects. For example, the JSON configuration provider can be used to map *appsettings.json* files to .NET objects and is used with [dependency injection](dependency-injection/overview.md). This enables the [options pattern](options.md), which uses classes to provide strongly typed access to groups of related settings. The default binder is reflection-based, but there's a [source generator alternative](configuration-generator.md) that's easy to enable.
6667

6768
.NET configuration provides various abstractions. Consider the following interfaces:
6869

@@ -84,7 +85,7 @@ The binder can use different approaches to process configuration values:​
8485
> - Properties are ignored if they have private setters or their type can't be converted.
8586
> - Properties without corresponding configuration keys are ignored.
8687
87-
#### Binding hierarchies
88+
### Binding hierarchies
8889

8990
Configuration values can contain hierarchical data. Hierarchical objects are represented with the use of the `:` delimiter in the configuration keys. To access a configuration value, use the `:` character to delimit a hierarchy. For example, consider the following configuration values:
9091

@@ -110,7 +111,61 @@ The following table represents example keys and their corresponding values for t
110111
| `"Parent:Child:Name"` | `"Example"` |
111112
| `"Parent:Child:GrandChild:Age"` | `3` |
112113

113-
### Basic example
114+
### Advanced binding scenarios
115+
116+
The configuration binder has specific behaviors and limitations when working with certain types. This section includes the following subsections:
117+
118+
- [Bind to dictionaries](#bind-to-dictionaries)
119+
- [Dictionary keys with colons](#dictionary-keys-with-colons)
120+
- [Bind to IReadOnly* types](#bind-to-ireadonly-types)
121+
- [Bind with parameterized constructors](#bind-with-parameterized-constructors)
122+
123+
#### Bind to dictionaries
124+
125+
When you bind configuration to a <xref:System.Collections.Generic.Dictionary`2> where the value is a mutable collection type (like arrays or lists), repeated binds to the same key extend the collection values instead of replacing them.
126+
127+
The following example demonstrates this behavior:
128+
129+
:::code language="csharp" source="snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs" id="DictionaryWithCollectionValues":::
130+
131+
For more information, see [Binding config to dictionary extends values](../compatibility/extensions/7.0/config-bind-dictionary.md).
132+
133+
#### Dictionary keys with colons
134+
135+
The colon (`:`) character is reserved as a hierarchy delimiter in configuration keys. This means you can't use colons in dictionary keys when binding configuration. If your keys contain colons (such as URLs or other formatted identifiers), the configuration system interprets them as hierarchy paths rather than literal characters. Consider the following workarounds:
136+
137+
- Use alternative delimiter characters (such as double underscores `__`) in your configuration keys and transform them programmatically if needed.
138+
- Manually deserialize the configuration as raw JSON using <xref:System.Text.Json> or a similar library, which supports colons in keys.
139+
- Create a custom mapping layer that translates safe keys to your desired keys with colons.
140+
141+
#### Bind to IReadOnly\* types
142+
143+
The configuration binder doesn't support binding directly to `IReadOnlyList<T>`, `IReadOnlyDictionary<TKey, TValue>`, or other read-only collection interfaces. These interfaces lack the mechanisms the binder needs to populate the collections.
144+
145+
To work with read-only collections, use mutable types for the properties that the binder populates, then expose them as read-only interfaces for consumers:
146+
147+
:::code language="csharp" source="snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs" id="IReadOnlyCollections":::
148+
149+
The configuration class implementation:
150+
151+
:::code language="csharp" source="snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs" id="SettingsWithReadOnly":::
152+
153+
This approach allows the binder to populate the mutable `List<string>` while presenting an immutable interface to consumers through `IReadOnlyList<string>`.
154+
155+
#### Bind with parameterized constructors
156+
157+
Starting with .NET 7, the configuration binder supports binding to types with a single public parameterized constructor. This enables immutable types and records to be populated directly from configuration:
158+
159+
:::code language="csharp" source="snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs" id="ParameterizedConstructor":::
160+
161+
The immutable settings class:
162+
163+
:::code language="csharp" source="snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs" id="AppSettings":::
164+
165+
> [!IMPORTANT]
166+
> The binder only supports types with a single public parameterized constructor. If a type has multiple public parameterized constructors, the binder cannot determine which one to use and binding will fail. Use either a single parameterized constructor or a parameterless constructor with property setters.
167+
168+
## Basic example
114169

115170
To access configuration values in their basic form, without the assistance of the _generic host_ approach, use the <xref:Microsoft.Extensions.Configuration.ConfigurationBuilder> type directly.
116171

@@ -148,7 +203,7 @@ The `Settings` object is shaped as follows:
148203

149204
:::code source="snippets/configuration/console-raw/NestedSettings.cs":::
150205

151-
### Basic example with hosting
206+
## Basic example with hosting
152207

153208
To access the `IConfiguration` value, you can rely again on the [`Microsoft.Extensions.Hosting`](https://www.nuget.org/packages/Microsoft.Extensions.Hosting) NuGet package. Create a new console application, and paste the following project file contents into it:
154209

@@ -173,7 +228,7 @@ When you run this application, the `Host.CreateApplicationBuilder` defines the b
173228
> [!TIP]
174229
> Using the raw `IConfiguration` instance in this way, while convenient, doesn't scale very well. When applications grow in complexity, and their corresponding configurations become more complex, we recommend that you use the [_options pattern_](options.md) as an alternative.
175230
176-
### Basic example with hosting and using the indexer API
231+
## Basic example with hosting and using the indexer API
177232

178233
Consider the same _appsettings.json_ file contents from the previous example:
179234

@@ -189,17 +244,17 @@ The values are accessed using the indexer API where each key is a string, and th
189244

190245
The following table shows the configuration providers available to .NET Core apps.
191246

192-
| Provider | Provides configuration from |
193-
|------------------------------------------------------------------------------------------------------------------------|------------------------------------|
194-
| [Azure App configuration provider](/azure/azure-app-configuration/quickstart-aspnet-core-app) | Azure App Configuration |
195-
| [Azure Key Vault configuration provider](/azure/key-vault/general/tutorial-net-virtual-machine) | Azure Key Vault |
196-
| [Command-line configuration provider](configuration-providers.md#command-line-configuration-provider) | Command-line parameters |
197-
| [Custom configuration provider](custom-configuration-provider.md) | Custom source |
198-
| [Environment Variables configuration provider](configuration-providers.md#environment-variable-configuration-provider) | Environment variables |
199-
| [File configuration provider](configuration-providers.md#file-configuration-provider) | JSON, XML, and INI files |
200-
| [Key-per-file configuration provider](configuration-providers.md#key-per-file-configuration-provider) | Directory files |
201-
| [Memory configuration provider](configuration-providers.md#memory-configuration-provider) | In-memory collections |
202-
| [App secrets (Secret Manager)](/aspnet/core/security/app-secrets) | File in the user profile directory |
247+
| Configuration provider | Provides configuration from |
248+
|--------------------------------------------------------------------------------|-----------------------------|
249+
| [Azure App Configuration](/azure/azure-app-configuration/quickstart-aspnet-core-app) | Azure App Configuration |
250+
| [Azure Key Vault](/azure/key-vault/general/tutorial-net-virtual-machine) | Azure Key Vault |
251+
| [Command-line](configuration-providers.md#command-line-configuration-provider) | Command-line parameters |
252+
| [Custom](custom-configuration-provider.md) | Custom source |
253+
| [Environment variables](configuration-providers.md#environment-variable-configuration-provider) | Environment variables |
254+
| [File](configuration-providers.md#file-configuration-provider) | JSON, XML, and INI files |
255+
| [Key-per-file](configuration-providers.md#key-per-file-configuration-provider) | Directory files |
256+
| [Memory](configuration-providers.md#memory-configuration-provider) | In-memory collections |
257+
| [App secrets (secret manager)](/aspnet/core/security/app-secrets) | File in the user profile directory |
203258

204259
> [!TIP]
205260
> The order in which configuration providers are added matters. When multiple configuration providers are used and more than one provider specifies the same key, the last one added is used.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.2" />
12+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.2" />
13+
</ItemGroup>
14+
15+
</Project>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using Microsoft.Extensions.Configuration;
2+
3+
// <DictionaryWithCollectionValues>
4+
IConfiguration config = new ConfigurationBuilder()
5+
.AddInMemoryCollection()
6+
.Build();
7+
8+
config["Queue:0"] = "Value1";
9+
var dict = new Dictionary<string, string[]>() { { "Queue", new[] { "InitialValue" } } };
10+
11+
Console.WriteLine("=== Dictionary Binding with Collection Values ===");
12+
Console.WriteLine($"Initially: {string.Join(", ", dict["Queue"])}");
13+
14+
// In .NET 7+, binding extends the collection instead of replacing it.
15+
config.Bind(dict);
16+
Console.WriteLine($"After Bind: {string.Join(", ", dict["Queue"])}");
17+
18+
config["Queue:1"] = "Value2";
19+
config.Bind(dict);
20+
Console.WriteLine($"After 2nd Bind: {string.Join(", ", dict["Queue"])}");
21+
// </DictionaryWithCollectionValues>
22+
23+
Console.WriteLine();
24+
25+
// <IReadOnlyCollections>
26+
Console.WriteLine("=== IReadOnly* Types (NOT Directly Supported) ===");
27+
28+
var readonlyConfig = new ConfigurationBuilder()
29+
.AddInMemoryCollection(new Dictionary<string, string?>
30+
{
31+
["Settings:Values:0"] = "Item1",
32+
["Settings:Values:1"] = "Item2",
33+
["Settings:Values:2"] = "Item3",
34+
})
35+
.Build();
36+
37+
// This class uses List<string> for binding, exposes as IReadOnlyList<string>.
38+
var settings = new SettingsWithReadOnly();
39+
readonlyConfig.GetSection("Settings").Bind(settings);
40+
41+
Console.WriteLine("Values bound to mutable List, exposed as IReadOnlyList:");
42+
foreach (var value in settings.ValuesReadOnly)
43+
{
44+
Console.WriteLine($" {value}");
45+
}
46+
// </IReadOnlyCollections>
47+
48+
Console.WriteLine();
49+
50+
// <ParameterizedConstructor>
51+
Console.WriteLine("=== Parameterized Constructor Binding ===");
52+
53+
var ctorConfig = new ConfigurationBuilder()
54+
.AddInMemoryCollection(new Dictionary<string, string?>
55+
{
56+
["AppSettings:Name"] = "MyApp",
57+
["AppSettings:MaxConnections"] = "100",
58+
["AppSettings:Timeout"] = "30"
59+
})
60+
.Build();
61+
62+
// Binding to a type with a single parameterized constructor
63+
var appSettings = ctorConfig.GetSection("AppSettings").Get<AppSettings>();
64+
if (appSettings != null)
65+
{
66+
Console.WriteLine($"Name: {appSettings.Name}");
67+
Console.WriteLine($"MaxConnections: {appSettings.MaxConnections}");
68+
Console.WriteLine($"Timeout: {appSettings.Timeout}");
69+
}
70+
// </ParameterizedConstructor>
71+
72+
// <SettingsWithReadOnly>
73+
class SettingsWithReadOnly
74+
{
75+
// Use mutable type for binding
76+
public List<string> Values { get; set; } = [];
77+
78+
// Expose as read-only for consumers
79+
public IReadOnlyList<string> ValuesReadOnly => Values;
80+
}
81+
// </SettingsWithReadOnly>
82+
83+
// <AppSettings>
84+
// Immutable type with single parameterized constructor.
85+
class AppSettings
86+
{
87+
public string Name { get; }
88+
public int MaxConnections { get; }
89+
public int Timeout { get; }
90+
91+
public AppSettings(string name, int maxConnections, int timeout)
92+
{
93+
Name = name;
94+
MaxConnections = maxConnections;
95+
Timeout = timeout;
96+
}
97+
}
98+
// </AppSettings>

0 commit comments

Comments
 (0)