diff --git a/docs/core/compatibility/extensions/7.0/config-bind-dictionary.md b/docs/core/compatibility/extensions/7.0/config-bind-dictionary.md index 810f1f6ef4b15..0b64cc5c87cde 100644 --- a/docs/core/compatibility/extensions/7.0/config-bind-dictionary.md +++ b/docs/core/compatibility/extensions/7.0/config-bind-dictionary.md @@ -5,7 +5,7 @@ ms.date: 08/02/2023 --- # Binding config to dictionary extends values -When binding a configuration using a 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. +When binding a configuration using a 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. ## Version introduced diff --git a/docs/core/extensions/configuration.md b/docs/core/extensions/configuration.md index e776960db98b6..b83db3d94099a 100644 --- a/docs/core/extensions/configuration.md +++ b/docs/core/extensions/configuration.md @@ -3,6 +3,7 @@ title: Configuration description: Learn how to use the Configuration API to configure .NET applications. Explore various inbuilt configuration providers. ms.date: 10/09/2024 ms.topic: overview +ai-usage: ai-assisted --- # Configuration in .NET @@ -14,7 +15,7 @@ Configuration in .NET is performed using one or more [configuration providers](# - [Azure Key Vault](/azure/key-vault/general/overview) - [Azure App Configuration](/azure/azure-app-configuration/overview) - Command-line arguments -- Custom providers, installed or created +- Custom providers (installed or created) - Directory files - In-memory .NET objects - Third-party providers @@ -30,7 +31,7 @@ Given one or more configuration sources, the and related types. +.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 and related types. :::code source="snippets/configuration/console-basic-builder/Program.cs"::: @@ -41,11 +42,11 @@ The preceding code: - Calls the method to create an instance. - Writes the value of the `SomeKey` key to the console. -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). +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). -### Alternative hosting approach +## Alternative hosting approach -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: +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: :::code source="snippets/configuration/console/Program.cs" highlight="3"::: @@ -60,9 +61,9 @@ The - Properties are ignored if they have private setters or their type can't be converted. > - Properties without corresponding configuration keys are ignored. -#### Binding hierarchies +### Binding hierarchies 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: @@ -110,7 +111,61 @@ The following table represents example keys and their corresponding values for t | `"Parent:Child:Name"` | `"Example"` | | `"Parent:Child:GrandChild:Age"` | `3` | -### Basic example +### Advanced binding scenarios + +The configuration binder has specific behaviors and limitations when working with certain types. This section includes the following subsections: + +- [Bind to dictionaries](#bind-to-dictionaries) +- [Dictionary keys with colons](#dictionary-keys-with-colons) +- [Bind to IReadOnly* types](#bind-to-ireadonly-types) +- [Bind with parameterized constructors](#bind-with-parameterized-constructors) + +#### Bind to dictionaries + +When you bind configuration to a 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. + +The following example demonstrates this behavior: + +:::code language="csharp" source="snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs" id="DictionaryWithCollectionValues"::: + +For more information, see [Binding config to dictionary extends values](../compatibility/extensions/7.0/config-bind-dictionary.md). + +#### Dictionary keys with colons + +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: + +- Use alternative delimiter characters (such as double underscores `__`) in your configuration keys and transform them programmatically if needed. +- Manually deserialize the configuration as raw JSON using or a similar library, which supports colons in keys. +- Create a custom mapping layer that translates safe keys to your desired keys with colons. + +#### Bind to IReadOnly\* types + +The configuration binder doesn't support binding directly to `IReadOnlyList`, `IReadOnlyDictionary`, or other read-only collection interfaces. These interfaces lack the mechanisms the binder needs to populate the collections. + +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: + +:::code language="csharp" source="snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs" id="IReadOnlyCollections"::: + +The configuration class implementation: + +:::code language="csharp" source="snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs" id="SettingsWithReadOnly"::: + +This approach allows the binder to populate the mutable `List` while presenting an immutable interface to consumers through `IReadOnlyList`. + +#### Bind with parameterized constructors + +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: + +:::code language="csharp" source="snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs" id="ParameterizedConstructor"::: + +The immutable settings class: + +:::code language="csharp" source="snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs" id="AppSettings"::: + +> [!IMPORTANT] +> 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. + +## Basic example To access configuration values in their basic form, without the assistance of the _generic host_ approach, use the type directly. @@ -148,7 +203,7 @@ The `Settings` object is shaped as follows: :::code source="snippets/configuration/console-raw/NestedSettings.cs"::: -### Basic example with hosting +## Basic example with hosting 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: @@ -173,7 +228,7 @@ When you run this application, the `Host.CreateApplicationBuilder` defines the b > [!TIP] > 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. -### Basic example with hosting and using the indexer API +## Basic example with hosting and using the indexer API Consider the same _appsettings.json_ file contents from the previous example: @@ -189,17 +244,17 @@ The values are accessed using the indexer API where each key is a string, and th The following table shows the configuration providers available to .NET Core apps. -| Provider | Provides configuration from | -|------------------------------------------------------------------------------------------------------------------------|------------------------------------| -| [Azure App configuration provider](/azure/azure-app-configuration/quickstart-aspnet-core-app) | Azure App Configuration | -| [Azure Key Vault configuration provider](/azure/key-vault/general/tutorial-net-virtual-machine) | Azure Key Vault | -| [Command-line configuration provider](configuration-providers.md#command-line-configuration-provider) | Command-line parameters | -| [Custom configuration provider](custom-configuration-provider.md) | Custom source | -| [Environment Variables configuration provider](configuration-providers.md#environment-variable-configuration-provider) | Environment variables | -| [File configuration provider](configuration-providers.md#file-configuration-provider) | JSON, XML, and INI files | -| [Key-per-file configuration provider](configuration-providers.md#key-per-file-configuration-provider) | Directory files | -| [Memory configuration provider](configuration-providers.md#memory-configuration-provider) | In-memory collections | -| [App secrets (Secret Manager)](/aspnet/core/security/app-secrets) | File in the user profile directory | +| Configuration provider | Provides configuration from | +|--------------------------------------------------------------------------------|-----------------------------| +| [Azure App Configuration](/azure/azure-app-configuration/quickstart-aspnet-core-app) | Azure App Configuration | +| [Azure Key Vault](/azure/key-vault/general/tutorial-net-virtual-machine) | Azure Key Vault | +| [Command-line](configuration-providers.md#command-line-configuration-provider) | Command-line parameters | +| [Custom](custom-configuration-provider.md) | Custom source | +| [Environment variables](configuration-providers.md#environment-variable-configuration-provider) | Environment variables | +| [File](configuration-providers.md#file-configuration-provider) | JSON, XML, and INI files | +| [Key-per-file](configuration-providers.md#key-per-file-configuration-provider) | Directory files | +| [Memory](configuration-providers.md#memory-configuration-provider) | In-memory collections | +| [App secrets (secret manager)](/aspnet/core/security/app-secrets) | File in the user profile directory | > [!TIP] > 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. diff --git a/docs/core/extensions/snippets/configuration/binding-scenarios/DictionaryBinding/DictionaryBinding.csproj b/docs/core/extensions/snippets/configuration/binding-scenarios/DictionaryBinding/DictionaryBinding.csproj new file mode 100644 index 0000000000000..f3dc8bbb1bba2 --- /dev/null +++ b/docs/core/extensions/snippets/configuration/binding-scenarios/DictionaryBinding/DictionaryBinding.csproj @@ -0,0 +1,15 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + diff --git a/docs/core/extensions/snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs b/docs/core/extensions/snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs new file mode 100644 index 0000000000000..2ace93416077f --- /dev/null +++ b/docs/core/extensions/snippets/configuration/binding-scenarios/DictionaryBinding/Program.cs @@ -0,0 +1,98 @@ +using Microsoft.Extensions.Configuration; + +// +IConfiguration config = new ConfigurationBuilder() + .AddInMemoryCollection() + .Build(); + +config["Queue:0"] = "Value1"; +var dict = new Dictionary() { { "Queue", new[] { "InitialValue" } } }; + +Console.WriteLine("=== Dictionary Binding with Collection Values ==="); +Console.WriteLine($"Initially: {string.Join(", ", dict["Queue"])}"); + +// In .NET 7+, binding extends the collection instead of replacing it. +config.Bind(dict); +Console.WriteLine($"After Bind: {string.Join(", ", dict["Queue"])}"); + +config["Queue:1"] = "Value2"; +config.Bind(dict); +Console.WriteLine($"After 2nd Bind: {string.Join(", ", dict["Queue"])}"); +// + +Console.WriteLine(); + +// +Console.WriteLine("=== IReadOnly* Types (NOT Directly Supported) ==="); + +var readonlyConfig = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["Settings:Values:0"] = "Item1", + ["Settings:Values:1"] = "Item2", + ["Settings:Values:2"] = "Item3", + }) + .Build(); + +// This class uses List for binding, exposes as IReadOnlyList. +var settings = new SettingsWithReadOnly(); +readonlyConfig.GetSection("Settings").Bind(settings); + +Console.WriteLine("Values bound to mutable List, exposed as IReadOnlyList:"); +foreach (var value in settings.ValuesReadOnly) +{ + Console.WriteLine($" {value}"); +} +// + +Console.WriteLine(); + +// +Console.WriteLine("=== Parameterized Constructor Binding ==="); + +var ctorConfig = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["AppSettings:Name"] = "MyApp", + ["AppSettings:MaxConnections"] = "100", + ["AppSettings:Timeout"] = "30" + }) + .Build(); + +// Binding to a type with a single parameterized constructor +var appSettings = ctorConfig.GetSection("AppSettings").Get(); +if (appSettings != null) +{ + Console.WriteLine($"Name: {appSettings.Name}"); + Console.WriteLine($"MaxConnections: {appSettings.MaxConnections}"); + Console.WriteLine($"Timeout: {appSettings.Timeout}"); +} +// + +// +class SettingsWithReadOnly +{ + // Use mutable type for binding + public List Values { get; set; } = []; + + // Expose as read-only for consumers + public IReadOnlyList ValuesReadOnly => Values; +} +// + +// +// Immutable type with single parameterized constructor. +class AppSettings +{ + public string Name { get; } + public int MaxConnections { get; } + public int Timeout { get; } + + public AppSettings(string name, int maxConnections, int timeout) + { + Name = name; + MaxConnections = maxConnections; + Timeout = timeout; + } +} +//