Freigeben über


Konfigurationsquellengenerator

Ab .NET 8 wurde ein Konfigurationsbindungsquellengenerator eingeführt, der bestimmte Anrufwebsites abfangen und deren Funktionalität generiert. Dieses Feature bietet eine native Vorablaufzeit (AOT) und eine kürzungsfreundliche Möglichkeit zur Verwendung des Konfigurationsordners, ohne dass die spiegelbasierte Implementierung verwendet wird. Reflection erfordert eine dynamische Codegenerierung, die in AOT-Szenarien nicht unterstützt wird.

Dieses Feature ist mit der Einführung von C#-Interceptors möglich, die in C# 12 eingeführt wurden. Interceptors ermöglichen es dem Compiler, Quellcode zu generieren, der bestimmte Aufrufe abfangen und durch generierten Code ersetzt.

Aktivieren des Konfigurationsquellgenerators

Um den Konfigurationsquellgenerator zu aktivieren, fügen Sie der Projektdatei die folgende Eigenschaft hinzu:

<PropertyGroup>
    <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>

Wenn der Konfigurationsquellgenerator aktiviert ist, generiert der Compiler eine Quelldatei, die den Konfigurationsbindungscode enthält. Die generierte Quelle fängt Bindungs-APIs aus den folgenden Klassen ab:

Mit anderen Worten: Alle APIs, die schließlich in diese verschiedenen Bindungsmethoden aufrufen, werden abgefangen und durch generierten Code ersetzt.

Beispielverwendung

Erwägen Sie eine .NET-Konsolenanwendung, die für die Veröffentlichung als systemeigene AOT-App konfiguriert ist. Der folgende Code veranschaulicht die Verwendung des Konfigurationsquellgenerators zum Binden von Konfigurationseinstellungen:

<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>

Die vorherige Projektdatei ermöglicht den Konfigurationsquellgenerator durch Festlegen der EnableConfigurationBindingGenerator Eigenschaft auf true.

Betrachten Sie als Nächstes die Program.cs Datei:

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/

Der vorangehende Code:

  • Instanziiert eine Konfigurations-Generator-Instanz.
  • Ruft AddInMemoryCollection drei Konfigurationsquellwerte auf und definiert sie.
  • Aufrufe Build() zum Erstellen der Konfiguration.
  • Verwendet die ConfigurationBinder.Bind Methode, um das Settings Objekt an die Konfigurationswerte zu binden.

Wenn die Anwendung erstellt wird, fängt der Konfigurationsquellgenerator den Aufruf Bind ab und generiert den Bindungscode.

Wichtig

Wenn die PublishAot Eigenschaft auf true (oder andere AOT-Warnungen aktiviert) festgelegt ist und die EnabledConfigurationBindingGenerator Eigenschaft auf " false, wird die Warnung IL2026 ausgelöst. Diese Warnung gibt an, dass Elemente mit RequiresUnreferencedCode zugeschrieben werden, wenn sie gekürzt werden. Weitere Informationen finden Sie unter IL2026.

Erkunden des generierten Quellcodes

Der folgende Code wird vom Konfigurationsquellgenerator für das vorherige Beispiel generiert:

// <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.
    }
}

Hinweis

Dieser generierte Code kann basierend auf der Version des Konfigurationsquellgenerators geändert werden.

Der generierte Code enthält die BindingExtensions Klasse, die die BindCore Methode enthält, die die tatsächliche Bindung ausführt. Die Bind_Settings Methode ruft die BindCore Methode auf und wandelt die Instanz in den angegebenen Typ um.

Um den generierten Code anzuzeigen, legen Sie die <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> Datei in der Projektdatei fest. Dadurch wird sichergestellt, dass die Dateien für die Überprüfung für den Entwickler sichtbar sind. Sie können den generierten Code auch im Projektmappen-Explorer von Visual Studio unter dem Knoten "Dependencies>Analyzers>Microsoft.Extensions.Configuration.Binder.SourceGeneration" ihres Projekts anzeigen.

Siehe auch