Delen via


Configuratiebrongenerator

Vanaf .NET 8 is er een configuratiebindingsbrongenerator geïntroduceerd die specifieke oproepsites onderschept en hun functionaliteit genereert. Deze functie biedt een native ahead-of-time (AOT) en trimvriendelijke manier om de configuratiebinding te gebruiken, zonder het gebruik van de implementatie op basis van weerspiegeling. Reflectie vereist dynamische codegeneratie, die niet wordt ondersteund in AOT-scenario's.

Deze functie is mogelijk met de komst van C#-interceptors die zijn geïntroduceerd in C# 12. Met interceptors kan de compiler broncode genereren die specifieke aanroepen onderschept en deze vervangt door gegenereerde code.

De configuratiebrongenerator inschakelen

Als u de configuratiebrongenerator wilt inschakelen, voegt u de volgende eigenschap toe aan uw projectbestand:

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

Wanneer de configuratiebrongenerator is ingeschakeld, genereert de compiler een bronbestand dat de configuratiebindingscode bevat. De gegenereerde bron onderschept binding-API's uit de volgende klassen:

Met andere woorden, alle API's die uiteindelijk in deze verschillende bindingsmethoden worden aangeroepen, worden onderschept en vervangen door gegenereerde code.

Voorbeeld van gebruik

Overweeg een .NET-consoletoepassing die is geconfigureerd om te publiceren als een systeemeigen AOT-app. De volgende code laat zien hoe u de configuratiebrongenerator gebruikt om configuratie-instellingen te binden:

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

Met het voorgaande projectbestand wordt de configuratiebrongenerator ingeschakeld door de EnableConfigurationBindingGenerator eigenschap in te stellen op true.

Houd vervolgens rekening met het Program.cs-bestand :

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/

Met de voorgaande code wordt:

  • Instantie van een instantie van de opbouwfunctie voor configuraties.
  • Roept AddInMemoryCollection drie configuratiebronwaarden aan en definieert deze.
  • Aanroepen Build() om de configuratie te bouwen.
  • Hiermee wordt de ConfigurationBinder.Bind methode gebruikt om het Settings object te binden aan de configuratiewaarden.

Wanneer de toepassing is gebouwd, onderschept de configuratiebrongenerator de aanroep naar Bind en genereert de bindingscode.

Belangrijk

Wanneer de PublishAot eigenschap is ingesteld op true (of andere AOT-waarschuwingen zijn ingeschakeld) en de EnabledConfigurationBindingGenerator eigenschap is ingesteld falseop , wordt er een waarschuwing IL2026 gegenereerd. Deze waarschuwing geeft aan dat leden worden toegeschreven aan RequiresUnreferencedCode kan breken bij het bijsnijden. Zie IL2026 voor meer informatie.

De gegenereerde broncode verkennen

De volgende code wordt gegenereerd door de configuratiebrongenerator voor het voorgaande voorbeeld:

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

Notitie

Deze gegenereerde code kan worden gewijzigd op basis van de versie van de configuratiebrongenerator.

De gegenereerde code bevat de BindingExtensions klasse, die de BindCore methode bevat waarmee de werkelijke binding wordt uitgevoerd. De Bind_Settings methode roept de BindCore methode aan en cast het exemplaar naar het opgegeven type.

Als u de gegenereerde code wilt zien, stelt u het <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> in het projectbestand in. Dit zorgt ervoor dat de bestanden zichtbaar zijn voor de ontwikkelaar voor inspectie. U kunt de gegenereerde code ook bekijken in Solution Explorer van Visual Studio onder het knooppunt Dependencies>Analyzers>Microsoft.Extensions.Configuration.Binder.SourceGeneration van uw project.

Zie ook