Générateur de source de configuration
À compter de .NET 8, un générateur de source de liaison de configuration a été introduit qui intercepte des sites d’appel spécifiques et génère leurs fonctionnalités. Cette fonctionnalité fournit un moyen natif avant-temps (AOT) et convivial d’utiliser le classeur de configuration, sans utiliser l’implémentation basée sur la réflexion. La réflexion nécessite une génération de code dynamique, qui n’est pas prise en charge dans les scénarios AOT.
Cette fonctionnalité est possible avec l’avènement des intercepteurs C# qui ont été introduits dans C# 12. Les intercepteurs permettent au compilateur de générer du code source qui intercepte des appels spécifiques et les remplace par du code généré.
Activer le générateur de source de configuration
Pour activer le générateur de source de configuration, ajoutez la propriété suivante à votre fichier projet :
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
Lorsque le générateur de source de configuration est activé, le compilateur génère un fichier source qui contient le code de liaison de configuration. La source générée intercepte les API de liaison à partir des classes suivantes :
- Microsoft.Extensions.Configuration.ConfigurationBinder
- Microsoft.Extensions.DependencyInjection.OptionsBuilderConfigurationExtensions
- Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions
En d’autres termes, toutes les API qui appellent finalement ces différentes méthodes de liaison sont interceptées et remplacées par du code généré.
Exemple d’utilisation
Considérez une application console .NET configurée pour publier en tant qu’application AOT native. Le code suivant montre comment utiliser le générateur de source de configuration pour lier les paramètres de configuration :
<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>
Le fichier projet précédent active le générateur de source de configuration en définissant la propriété true
sur EnableConfigurationBindingGenerator
.
Ensuite, considérez le fichier 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/
Le code précédent :
- Instancie une instance de générateur de configuration.
- Appelle AddInMemoryCollection et définit trois valeurs sources de configuration.
- Appels Build() pour générer la configuration.
- Utilise la ConfigurationBinder.Bind méthode pour lier l’objet
Settings
aux valeurs de configuration.
Lorsque l’application est générée, le générateur de source de configuration intercepte l’appel Bind
et génère le code de liaison.
Important
Lorsque la PublishAot
propriété est définie true
sur (ou d’autres avertissements AOT sont activés) et que la EnabledConfigurationBindingGenerator
propriété est définie false
sur , l’avertissement IL2026
est déclenché. Cet avertissement indique que les membres sont attribués avec RequiresUnreferencedCode peut s’interrompre lors de la suppression. Pour plus d’informations, consultez IL2026.
Explorer le code généré par la source
Le code suivant est généré par le générateur de source de configuration pour l’exemple précédent :
// <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.
}
}
Remarque
Ce code généré est susceptible de changer en fonction de la version du générateur de source de configuration.
Le code généré contient la BindingExtensions
classe, qui contient la BindCore
méthode qui effectue la liaison réelle. La Bind_Settings
méthode appelle la BindCore
méthode et convertit l’instance en type spécifié.
Pour afficher le code généré, définissez le <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
fichier projet. Cela garantit que les fichiers sont visibles par le développeur pour inspection. Vous pouvez également afficher le code généré dans les Explorateur de solutions de Visual Studio sous le nœud Microsoft.Extensions.Configuration.Binder.SourceGeneration>> de votre projet.