Generador de origen de configuración
A partir de .NET 8, se introdujo un generador de origen de enlace de configuración que intercepta sitios de llamadas específicos y genera su funcionalidad. Esta característica proporciona una forma nativa anticipada de tiempo (AOT) y una manera fácil de usar el enlazador de configuración, sin el uso de la implementación basada en reflexión. La reflexión requiere la generación dinámica de código, que no se admite en escenarios de AOT.
Esta característica es posible con la llegada de interceptores de C# que se introdujeron en C# 12. Los interceptores permiten al compilador generar código fuente que intercepta llamadas específicas y los sustituye por el código generado.
Habilitación del generador de origen de configuración
Para habilitar el generador de origen de configuración, agregue la siguiente propiedad al archivo del proyecto:
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
Cuando el generador de origen de configuración está habilitado, el compilador genera un archivo de origen que contiene el código de enlace de configuración. El origen generado intercepta las API de enlace de las siguientes clases:
- Microsoft.Extensions.Configuration.ConfigurationBinder
- Microsoft.Extensions.DependencyInjection.OptionsBuilderConfigurationExtensions
- Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions
En otras palabras, todas las API que finalmente llaman a estos distintos métodos de enlace se interceptan y reemplazan por código generado.
Ejemplo de uso
Considere la posibilidad de usar una aplicación de consola de .NET configurada para publicarla como una aplicación AOT nativa. En el código siguiente se muestra cómo usar el generador de origen de configuración para enlazar las opciones de configuración:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>console_binder_gen</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.1" />
</ItemGroup>
</Project>
El archivo de proyecto anterior habilita el generador de origen de configuración estableciendo la EnableConfigurationBindingGenerator
propiedad true
en .
A continuación, considere el archivo Program.cs :
using Microsoft.Extensions.Configuration;
var builder = new ConfigurationBuilder()
.AddInMemoryCollection(initialData: [
new("port", "5001"),
new("enabled", "true"),
new("apiUrl", "https://jsonplaceholder.typicode.com/")
]);
var configuration = builder.Build();
var settings = new Settings();
configuration.Bind(settings);
// Write the values to the console.
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
// Enabled = True
// API URL = https://jsonplaceholder.typicode.com/
El código anterior:
- Crea una instancia de un generador de configuración.
- Llama AddInMemoryCollection a y define tres valores de origen de configuración.
- Llama Build() a para compilar la configuración.
- Usa el ConfigurationBinder.Bind método para enlazar el
Settings
objeto a los valores de configuración.
Cuando se compila la aplicación, el generador de origen de configuración intercepta la llamada a Bind
y genera el código de enlace.
Importante
Cuando la PublishAot
propiedad se establece en true
(o en cualquier otra advertencia de AOT está habilitada) y la EnabledConfigurationBindingGenerator
propiedad se establece false
en , se genera la advertencia IL2026
. Esta advertencia indica que los miembros se atribuyen a RequireUnreferencedCode al recortar. Para obtener más información, consulte IL2026.
Exploración del código fuente generado
El generador de origen de configuración genera el código siguiente para el ejemplo anterior:
// <auto-generated/>
#nullable enable annotations
#nullable disable warnings
// Suppress warnings about [Obsolete] member usage in generated code.
#pragma warning disable CS0612, CS0618
namespace System.Runtime.CompilerServices
{
using System;
using System.CodeDom.Compiler;
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "9.0.10.47305")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{
public InterceptsLocationAttribute(int version, string data)
{
}
}
}
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", "9.0.10.47305")]
file static class BindingExtensions
{
#region IConfiguration extensions.
/// <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.
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)
{
ValidateConfigurationKeys(typeof(global::Settings), s_configKeys_Settings, configuration, binderOptions);
if (configuration["Port"] is string value0 && !string.IsNullOrEmpty(value0))
{
instance.Port = ParseInt(value0, configuration.GetSection("Port").Path);
}
else if (defaultValueIfNotFound)
{
instance.Port = instance.Port;
}
if (configuration["Enabled"] is string value1 && !string.IsNullOrEmpty(value1))
{
instance.Enabled = ParseBool(value1, configuration.GetSection("Enabled").Path);
}
else if (defaultValueIfNotFound)
{
instance.Enabled = instance.Enabled;
}
if (configuration["ApiUrl"] is string value2)
{
instance.ApiUrl = value2;
}
else if (defaultValueIfNotFound)
{
var currentValue = instance.ApiUrl;
if (currentValue is not null)
{
instance.ApiUrl = currentValue;
}
}
}
/// <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)
{
if (binderOptions?.ErrorOnUnknownConfiguration is true)
{
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 int ParseInt(string value, string? path)
{
try
{
return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
catch (Exception exception)
{
throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception);
}
}
public static bool ParseBool(string value, string? path)
{
try
{
return bool.Parse(value);
}
catch (Exception exception)
{
throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception);
}
}
#endregion Core binding extensions.
}
}
Nota:
Este código generado está sujeto a cambios en función de la versión del generador de origen de configuración.
El código generado contiene la BindingExtensions
clase , que contiene el BindCore
método que realiza el enlace real. El Bind_Settings
método llama al BindCore
método y convierte la instancia en el tipo especificado.
Para ver el código generado, establezca en <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
el archivo del proyecto. Esto garantiza que los archivos sean visibles para el desarrollador para su inspección. También puede ver el código generado en el Explorador de soluciones de Visual Studio en el nodo Analizadores de dependencias>>de su proyecto Microsoft.Extensions.Configuration.Binder.SourceGeneration.