Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 70 additions & 66 deletions docs/core/extensions/configuration-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ The preceding code:
- Instantiates a configuration builder instance.
- Calls <xref:Microsoft.Extensions.Configuration.MemoryConfigurationBuilderExtensions.AddInMemoryCollection%2A> and defines three configuration source values.
- Calls <xref:Microsoft.Extensions.Configuration.IConfigurationBuilder.Build> to build the configuration.
- Uses the <xref:Microsoft.Extensions.Configuration.ConfigurationBinder.GetValue%2A?displayProperty=nameWithType> extension method to get the value for each configuration key.
- Uses the <xref:Microsoft.Extensions.Configuration.ConfigurationBinder.Bind%2A?displayProperty=nameWithType> method to bind the `Settings` object to the configuration values.

When the application is built, the configuration source generator intercepts the call to `GetValue<T>` and generates the binding code.
When the application is built, the configuration source generator intercepts the call to `Bind` and generates the binding code.

> [!IMPORTANT]
> When the `PublishAot` property is set to `true` (or any other AOT warnings are enabled) and the `EnabledConfigurationBindingGenerator` property is set to `false`, warning `IL2026` is raised. This warning indicates that members are attributed with [RequiresUnreferencedCode](xref:System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute) may break when trimming. For more information, see [IL2026](../deploying/trimming/trim-warnings/il2026.md).
Expand All @@ -72,13 +72,11 @@ namespace System.Runtime.CompilerServices
using System;
using System.CodeDom.Compiler;

[GeneratedCode(
"Microsoft.Extensions.Configuration.Binder.SourceGeneration",
"8.0.10.31311")]
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "9.0.10.47305")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{
public InterceptsLocationAttribute(string filePath, int line, int column)
public InterceptsLocationAttribute(int version, string data)
{
}
}
Expand All @@ -89,107 +87,113 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using Microsoft.Extensions.Configuration;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;

[GeneratedCode(
"Microsoft.Extensions.Configuration.Binder.SourceGeneration",
"8.0.10.31311")]
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "9.0.10.47305")]
file static class BindingExtensions
{
#region IConfiguration extensions.
/// <summary>
/// Extracts the value with the specified key and converts it to the specified type.
/// </summary>
[InterceptsLocation(@"C:\source\configuration\console-binder-gen\Program.cs", 12, 26)]
[InterceptsLocation(@"C:\source\configuration\console-binder-gen\Program.cs", 13, 29)]
[InterceptsLocation(@"C:\source\configuration\console-binder-gen\Program.cs", 14, 28)]
public static T? GetValue<T>(this IConfiguration configuration, string key) =>
(T?)(BindingExtensions.GetValueCore(configuration, typeof(T), key) ?? default(T));
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
[InterceptsLocation(1, "uDIs2gDFz/yEvxOzjNK4jnIBAABQcm9ncmFtLmNz")] // D:\source\WorkerService1\WorkerService1\Program.cs(13,15)
public static void Bind_Settings(this IConfiguration configuration, object? instance)
{
ArgumentNullException.ThrowIfNull(configuration);

if (instance is null)
{
return;
}

var typedObj = (global::Settings)instance;
BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, binderOptions: null);
}
#endregion IConfiguration extensions.

#region Core binding extensions.
public static object? GetValueCore(
this IConfiguration configuration, Type type, string key)
private readonly static Lazy<HashSet<string>> s_configKeys_Settings = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Port", "Enabled", "ApiUrl" });

public static void BindCore(IConfiguration configuration, ref global::Settings instance, bool defaultValueIfNotFound, BinderOptions? binderOptions)
{
if (configuration is null)
ValidateConfigurationKeys(typeof(global::Settings), s_configKeys_Settings, configuration, binderOptions);

if (configuration["Port"] is string value0 && !string.IsNullOrEmpty(value0))
{
throw new ArgumentNullException(nameof(configuration));
instance.Port = ParseInt(value0, configuration.GetSection("Port").Path);
}

IConfigurationSection section = configuration.GetSection(key);

if (section.Value is not string value)
else if (defaultValueIfNotFound)
{
return null;
instance.Port = instance.Port;
}

if (type == typeof(int))
if (configuration["Enabled"] is string value1 && !string.IsNullOrEmpty(value1))
{
return ParseInt(value, () => section.Path);
instance.Enabled = ParseBool(value1, configuration.GetSection("Enabled").Path);
}
else if (type == typeof(bool))
else if (defaultValueIfNotFound)
{
return ParseBool(value, () => section.Path);
instance.Enabled = instance.Enabled;
}
else if (type == typeof(global::System.Uri))

if (configuration["ApiUrl"] is string value2)
{
return ParseSystemUri(value, () => section.Path);
instance.ApiUrl = value2;
}
else if (defaultValueIfNotFound)
{
var currentValue = instance.ApiUrl;
if (currentValue is not null)
{
instance.ApiUrl = currentValue;
}
}

return null;
}

public static int ParseInt(
string value, Func<string?> getPath)

/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{
try
if (binderOptions?.ErrorOnUnknownConfiguration is true)
{
return int.Parse(
value,
NumberStyles.Integer,
CultureInfo.InvariantCulture);
}
catch (Exception exception)
{
throw new InvalidOperationException(
$"Failed to convert configuration value at " +
"'{getPath()}' to type '{typeof(int)}'.",
exception);
List<string>? temp = null;

foreach (IConfigurationSection section in configuration.GetChildren())
{
if (!keys.Value.Contains(section.Key))
{
(temp ??= new List<string>()).Add($"'{section.Key}'");
}
}

if (temp is not null)
{
throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
}
}
}

public static bool ParseBool(
string value, Func<string?> getPath)
public static int ParseInt(string value, string? path)
{
try
{
return bool.Parse(value);
return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
catch (Exception exception)
{
throw new InvalidOperationException(
$"Failed to convert configuration value at " +
"'{getPath()}' to type '{typeof(bool)}'.",
exception);
throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception);
}
}

public static global::System.Uri ParseSystemUri(
string value, Func<string?> getPath)
public static bool ParseBool(string value, string? path)
{
try
{
return new Uri(
value,
UriKind.RelativeOrAbsolute);
return bool.Parse(value);
}
catch (Exception exception)
{
throw new InvalidOperationException(
$"Failed to convert configuration value at " +
"'{getPath()}' to type '{typeof(global::System.Uri)}'.",
exception);
throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception);
}
}
#endregion Core binding extensions.
Expand All @@ -200,9 +204,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
> [!NOTE]
> This generated code is subject to change based on the version of the configuration source generator.

The generated code contains the `BindingExtensions` class, which contains the `GetValueCore` method that performs the actual binding. The `GetValue<T>` extension method calls the `GetValueCore` method and casts the result to the specified type.
The generated code contains the `BindingExtensions` class, which contains the `BindCore` method that performs the actual binding. The `Bind_Settings` method calls the `BindCore` method and casts the instance to the specified type.

To see the generated code, set the `<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>` in the project file. This ensures that the files are visible to the developer for inspection.
To see the generated code, set the `<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>` in the project file. This ensures that the files are visible to the developer for inspection. You can also view the generated code in Visual Studio's Solution Explorer under your project's `Dependencies` -> `Analyzers` -> `Microsoft.Extensions.Configuration.Binder.SourceGeneration` node.

## See also

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@

var configuration = builder.Build();

var port = configuration.GetValue<int>("port");
var enabled = configuration.GetValue<bool>("enabled");
var apiUrl = configuration.GetValue<Uri>("apiUrl");
var settings = new Settings();
configuration.Bind(settings);

// Write the values to the console.
Console.WriteLine($"Port = {port}");
Console.WriteLine($"Enabled = {enabled}");
Console.WriteLine($"API URL = {apiUrl}");
Console.WriteLine($"Port = {settings.Port}");
Console.WriteLine($"Enabled = {settings.Enabled}");
Console.WriteLine($"API URL = {settings.ApiUrl}");

class Settings
{
public int Port { get; set; }
public bool Enabled { get; set; }
public string? ApiUrl { get; set; }
}

// This will output the following:
// Port = 5001
Expand Down
Loading