Generator źródła konfiguracji
Począwszy od platformy .NET 8, wprowadzono generator źródła powiązania konfiguracji, który przechwytuje określone lokacje połączeń i generuje ich funkcjonalność. Ta funkcja zapewnia natywny przedterminowy (AOT) i przyjazny dla przycinania sposób korzystania z powiązania konfiguracji bez użycia implementacji opartej na odbiciu. Odbicie wymaga dynamicznego generowania kodu, które nie jest obsługiwane w scenariuszach AOT.
Ta funkcja jest możliwa dzięki pojawieniu się przechwytników języka C#, które zostały wprowadzone w języku C# 12. Przechwytywacze umożliwiają kompilatorowi generowanie kodu źródłowego, który przechwytuje określone wywołania i zastępuje je wygenerowanymi kodami.
Włączanie generatora źródła konfiguracji
Aby włączyć generator źródła konfiguracji, dodaj następującą właściwość do pliku projektu:
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
Po włączeniu generatora źródła konfiguracji kompilator generuje plik źródłowy zawierający kod powiązania konfiguracji. Wygenerowane źródło przechwytuje interfejsy API powiązań z następujących klas:
- Microsoft.Extensions.Configuration.ConfigurationBinder
- Microsoft.Extensions.DependencyInjection.OptionsBuilderConfigurationExtensions
- Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions
Innymi słowy, wszystkie interfejsy API, które ostatecznie wywołają te różne metody powiązania, są przechwytywane i zastępowane wygenerowanym kodem.
Przykładowe użycie
Rozważmy aplikację konsolową platformy .NET skonfigurowaną do publikowania jako natywna aplikacja AOT. Poniższy kod pokazuje, jak używać generatora źródła konfiguracji do powiązania ustawień konfiguracji:
<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>
Powyższy plik projektu włącza generator źródła konfiguracji, ustawiając EnableConfigurationBindingGenerator
właściwość na true
.
Następnie rozważ plik 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/
Powyższy kod ma następujące działanie:
- Tworzy wystąpienie konstruktora konfiguracji.
- Wywołuje AddInMemoryCollection i definiuje trzy wartości źródła konfiguracji.
- Wywołania Build() w celu skompilowania konfiguracji.
- ConfigurationBinder.Bind Używa metody do powiązania
Settings
obiektu z wartościami konfiguracji.
Gdy aplikacja zostanie skompilowana, generator źródła konfiguracji przechwytuje wywołanie Bind
i generuje kod powiązania.
Ważne
PublishAot
Gdy właściwość jest ustawiona na true
(lub inne ostrzeżenia AOT są włączone) i EnabledConfigurationBindingGenerator
właściwość jest ustawiona na false
, zostanie zgłoszone ostrzeżenieIL2026
. To ostrzeżenie wskazuje, że elementy członkowskie są przypisywane elementom RequiresUnreferencedCode może ulec awarii podczas przycinania. Aby uzyskać więcej informacji, zobacz IL2026.
Eksplorowanie kodu wygenerowanego przez źródło
Poniższy kod jest generowany przez generator źródła konfiguracji dla poprzedniego przykładu:
// <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.
}
}
Uwaga
Ten wygenerowany kod może ulec zmianie na podstawie wersji generatora źródła konfiguracji.
Wygenerowany kod zawiera klasę BindingExtensions
, która zawiera BindCore
metodę, która wykonuje rzeczywiste powiązanie. Metoda Bind_Settings
wywołuje metodę BindCore
i rzutuje wystąpienie do określonego typu.
Aby wyświetlić wygenerowany kod, ustaw element <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
w pliku projektu. Dzięki temu pliki są widoczne dla dewelopera w celu przeprowadzenia inspekcji. Wygenerowany kod można również wyświetlić w Eksplorator rozwiązań programu Visual Studio w obszarze Analizatory>zależności>projektu Microsoft.Extensions.Configuration.Binder.SourceGeneration.