Bagikan melalui


Generator sumber konfigurasi

Dimulai dengan .NET 8, generator sumber pengikatan konfigurasi diperkenalkan yang mencegat situs panggilan tertentu dan menghasilkan fungsionalitasnya. Fitur ini menyediakan cara native ahead-of-time (AOT) dan trim-friendly untuk menggunakan pengikat konfigurasi, tanpa menggunakan implementasi berbasis refleksi. Refleksi memerlukan pembuatan kode dinamis, yang tidak didukung dalam skenario AOT.

Fitur ini dimungkinkan dengan munculnya pencegat C# yang diperkenalkan di C# 12. Pencegat memungkinkan pengkompilasi untuk menghasilkan kode sumber yang mencegat panggilan tertentu dan menggantinya dengan kode yang dihasilkan.

Mengaktifkan generator sumber konfigurasi

Untuk mengaktifkan generator sumber konfigurasi, tambahkan properti berikut ke file proyek Anda:

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

Ketika generator sumber konfigurasi diaktifkan, pengkompilasi menghasilkan file sumber yang berisi kode pengikatan konfigurasi. Sumber yang dihasilkan mencegat API pengikatan dari kelas berikut:

Dengan kata lain, semua API yang akhirnya memanggil berbagai metode pengikatan ini disadap dan diganti dengan kode yang dihasilkan.

Contoh penggunaan

Pertimbangkan aplikasi konsol .NET yang dikonfigurasi untuk diterbitkan sebagai aplikasi AOT asli. Kode berikut menunjukkan cara menggunakan generator sumber konfigurasi untuk mengikat pengaturan konfigurasi:

<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.3" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.3" />
  </ItemGroup>

</Project>

File proyek sebelumnya memungkinkan generator sumber konfigurasi dengan mengatur EnableConfigurationBindingGenerator properti ke true.

Selanjutnya, pertimbangkan file 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/

Kode sebelumnya:

  • Membuat instans penyusun konfigurasi secara instans.
  • AddInMemoryCollection Memanggil dan menentukan tiga nilai sumber konfigurasi.
  • Build() Panggilan untuk membangun konfigurasi.
  • ConfigurationBinder.Bind Menggunakan metode untuk mengikat Settings objek ke nilai konfigurasi.

Ketika aplikasi dibuat, generator sumber konfigurasi mencegat panggilan ke Bind dan menghasilkan kode pengikatan.

Penting

PublishAot Ketika properti diatur ke true (atau peringatan AOT lainnya diaktifkan) dan EnabledConfigurationBindingGenerator properti diatur ke false, peringatan IL2026 dinaikkan. Peringatan ini menunjukkan bahwa anggota dikaitkan dengan RequiresUnreferencedCode dapat rusak saat pemangkasan. Untuk informasi selengkapnya, lihat IL2026.

Menjelajahi kode yang dihasilkan sumber

Kode berikut dihasilkan oleh generator sumber konfigurasi untuk contoh sebelumnya:

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

Catatan

Kode yang dihasilkan ini dapat berubah berdasarkan versi generator sumber konfigurasi.

Kode yang dihasilkan berisi BindingExtensions kelas , yang berisi BindCore metode yang melakukan pengikatan aktual. Metode ini Bind_Settings memanggil BindCore metode dan melemparkan instans ke jenis yang ditentukan.

Untuk melihat kode yang dihasilkan, atur <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> dalam file proyek. Ini memastikan bahwa file terlihat oleh pengembang untuk diperiksa. Anda juga dapat melihat kode yang dihasilkan di Penjelajah Solusi Visual Studio di bawah node Dependencies>Analyzers>Microsoft.Extensions.Configuration.Binder.SourceGeneration proyek Anda.

Lihat juga