Aracılığıyla paylaş


Yapılandırma kaynağı oluşturucu

.NET 8'den başlayarak, belirli çağrı sitelerini kesen ve işlevlerini oluşturan bir yapılandırma bağlama kaynak oluşturucu kullanıma sunulmuştur. Bu özellik, yansıma tabanlı uygulama kullanılmadan yapılandırma bağlayıcısını kullanmak için, zamanından önce (AOT) ve kırpma dostu bir yerel yol sağlar. Yansıma, AOT senaryolarında desteklenmeyen dinamik kod oluşturmayı gerektirir.

Bu özellik, C# 12'de tanıtılan C# önleyicilerinin geliştirilmesiyle mümkündür. Kesiciler, derleyicinin belirli çağrıları kesen ve bunları oluşturulan kodla değiştiren kaynak kodu oluşturmasına olanak sağlar.

Yapılandırma kaynağı oluşturucuyu etkinleştirme

Yapılandırma kaynağı oluşturucuyu etkinleştirmek için proje dosyanıza aşağıdaki özelliği ekleyin:

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

Yapılandırma kaynak oluşturucu etkinleştirildiğinde, derleyici yapılandırma bağlama kodunu içeren bir kaynak dosyası oluşturur. Oluşturulan kaynak, aşağıdaki sınıflardan API'leri bağlamayı durdurur:

Başka bir deyişle, sonunda bu çeşitli bağlama metodlarına çağrıda bulunan tüm API'ler engellenir ve oluşturulan kodla değiştirilir.

Örnek kullanım

Yerel AOT uygulaması olarak yayımlamak üzere yapılandırılmış bir .NET konsol uygulaması düşünün. Aşağıdaki kod, yapılandırma ayarlarını bağlamak için yapılandırma kaynak oluşturucunun nasıl kullanılacağını gösterir:

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

Önceki proje dosyası, EnableConfigurationBindingGenerator özelliğini true olarak ayarlayarak yapılandırma kaynağı oluşturucusunu etkinleştirir.

Ardından Program.cs dosyasını göz önünde bulundurun:

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/

Yukarıdaki kod:

  • Yapılandırma oluşturucu örneğini başlatır.
  • AddInMemoryCollection çağırır ve üç yapılandırma kaynağı değerini tanımlar.
  • Build() yapılandırmayı oluşturmak için çağrılar.
  • ConfigurationBinder.Bind yöntemini kullanarak Settings nesnesini yapılandırma değerlerine bağlar.

Uygulama oluşturulduğunda, yapılandırma kaynağı oluşturucu Bind çağrısını yakalar ve bağlama kodunu üretir.

Önemli

PublishAot olarak ayarlandığında (veya diğer AOT uyarıları etkinleştirildiğinde) ve EnabledConfigurationBindingGenerator özelliği false olarak ayarlandığında, uyarı IL2026 oluşturulur. Bu uyarı, RequiresUnreferencedCode niteliğine sahip üyelerin kırpma sırasında bozulabileceğini belirtir. Daha fazla bilgi için bkz . IL2026.

Kaynak tarafından oluşturulan kodu keşfetme

Yukarıdaki örnek için yapılandırma kaynağı oluşturucu tarafından aşağıdaki kod oluşturulur:

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

Not

Oluşturulan bu kod, yapılandırma kaynak oluşturucusunun sürümüne göre değiştirilebilir.

Oluşturulan kod, BindingExtensions sınıfını içerir ve bu sınıf, gerçek bağlamayı gerçekleştiren BindCore yöntemini içerir. Bind_Settings yöntemini çağırır BindCore ve örneği belirtilen türe dönüştürür.

Oluşturulan kodu görmek için proje dosyasına <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> yerleştirin. Bu, dosyaların inceleme için geliştirici tarafından görülebilmesini sağlar. Oluşturulan kodu, projenizin Bağımlılıklar AnalizcilerMicrosoft.Extensions.Configuration.Binder.SourceGeneration düğümü altında, Visual Studio'nun Çözüm Gezgini bölümünde de görüntüleyebilirsiniz.

Ayrıca bkz.