Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
Начиная с .NET 8, был представлен генератор источника привязки конфигурации, который перехватывает определенные сайты вызовов и создает их функциональные возможности. Эта функция предоставляет Native Ahead-Of-Time (AOT) и поддерживающий обрезку способ использования связывателя конфигурации без использования реализации на основе отражения. Для отражения требуется динамическое создание кода, которое не поддерживается в сценариях AOT.
Эта функция возможна с появлением перехватчиков C#, появившихся в C# 12. Перехватчики позволяют компилятору создавать исходный код, перехватывающий определенные вызовы и заменяющий их созданным кодом.
Включите генератор источника конфигурации
Чтобы включить генератор источника конфигурации, добавьте в файл проекта следующее свойство:
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
Если генератор источника конфигурации включен, компилятор создает исходный файл, содержащий код привязки конфигурации. Созданный источник перехватывает API привязки из следующих классов:
- Microsoft.Extensions.Configuration.ConfigurationBinder
- Microsoft.Extensions.DependencyInjection.OptionsBuilderConfigurationExtensions
- Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions
Другими словами, все API, которые в конечном итоге вызывают эти различные методы привязки, перехватываются и заменяются созданным кодом.
Пример использования
Рассмотрим консольное приложение .NET, настроенное для публикации в качестве собственного приложения AOT. В следующем коде показано, как использовать генератор источника конфигурации для привязки параметров конфигурации:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.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.DotNet.ILCompiler" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.5" />
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="10.0.5" />
</ItemGroup>
</Project>
Исходный файл проекта позволяет настроить генератор источника путем установки свойства EnableConfigurationBindingGenerator в значение true.
Затем рассмотрим файл 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/
Предыдущий код:
- Инициализирует экземпляр построителя конфигураций.
- Вызывает AddInMemoryCollection и определяет три конфигурационные значения источника.
- Вызов Build(), чтобы построить конфигурацию.
-
ConfigurationBinder.Bind Использует метод для привязки
Settingsобъекта к значениям конфигурации.
При создании приложения генератор источника конфигурации перехватывает вызов Bind и создает код привязки.
Внимание
Если для свойства PublishAot задано значение true (или включены другие предупреждения AOT), и если для свойства EnabledConfigurationBindingGenerator задано значение false, возникает предупреждение IL2026. Это предупреждение указывает на то, что члены, отнесенные к RequiresUnreferencedCode, могут привести к сбоям при обрезке. Дополнительные сведения см. в разделе IL2026.
Исследуйте сгенерированный исходный код
Следующий код создается генератором источника конфигурации для предыдущего примера:
// <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.
}
}
Примечание.
Этот созданный код подлежит изменению в зависимости от версии генератора источника конфигурации.
Созданный код содержит BindingExtensions класс, содержащий BindCore метод, выполняющий фактическую привязку. Метод Bind_Settings вызывает BindCore метод и приводит экземпляр к указанному типу.
Чтобы просмотреть сгенерированный код, установите <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> в файле проекта. Это гарантирует, что файлы видны разработчику для проверки. Вы также можете просмотреть созданный код в Обозревателе решений Visual Studio в узле Зависимостей>> вашего проекта.