Starting with .NET 8, a configuration binding source generator was introduced that intercepts specific call sites and generates their functionality. This feature provides a Native ahead-of-time (AOT) and trim-friendly way to use the configuration binder, without the use of the reflection-based implementation. Reflection requires dynamic code generation, which isn't supported in AOT scenarios.
This feature is possible with the advent of C# interceptors that were introduced in C# 12. Interceptors allow the compiler to generate source code that intercepts specific calls and substitutes them with generated code.
Enable the configuration source generator
To enable the configuration source generator, add the following property to your project file:
When the configuration source generator is enabled, the compiler generates a source file that contains the configuration binding code. The generated source intercepts binding APIs from the following classes:
In other words, all APIs that eventually call into these various binding methods are intercepted and replaced with generated code.
Example usage
Consider a .NET console application configured to publish as a native AOT app. The following code demonstrates how to use the configuration source generator to bind configuration settings:
Uses the ConfigurationBinder.Bind method to bind the Settings object to the configuration values.
When the application is built, the configuration source generator intercepts the call to Bind and generates the binding code.
Important
When the PublishAot property is set to true (or any other AOT warnings are enabled) and the EnabledConfigurationBindingGenerator property is set to false, warning IL2026 is raised. This warning indicates that members are attributed with RequiresUnreferencedCode may break when trimming. For more information, see IL2026.
Explore the source generated code
The following code is generated by the configuration source generator for the preceding example:
C#
// <auto-generated/>#nullable enable annotations#nullable disable warnings// Suppress warnings about [Obsolete] member usage in generated code.#pragmawarning disable CS0612, CS0618namespaceSystem.Runtime.CompilerServices
{
using System;
using System.CodeDom.Compiler;
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "9.0.10.47305")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealedclassInterceptsLocationAttribute : Attribute
{
publicInterceptsLocationAttribute(int version, string data)
{
}
}
}
namespaceMicrosoft.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 staticclassBindingExtensions
{
#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)publicstaticvoidBind_Settings(this IConfiguration configuration, object? instance)
{
ArgumentNullException.ThrowIfNull(configuration);
if (instance isnull)
{
return;
}
var typedObj = (global::Settings)instance;
BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, binderOptions: null);
}
#endregion IConfiguration extensions.#region Core binding extensions.privatereadonlystatic Lazy<HashSet<string>> s_configKeys_Settings = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Port", "Enabled", "ApiUrl" });
publicstaticvoidBindCore(IConfiguration configuration, refglobal::Settings instance, bool defaultValueIfNotFound, BinderOptions? binderOptions)
{
ValidateConfigurationKeys(typeof(global::Settings), s_configKeys_Settings, configuration, binderOptions);
if (configuration["Port"] isstring value0 && !string.IsNullOrEmpty(value0))
{
instance.Port = ParseInt(value0, configuration.GetSection("Port").Path);
}
elseif (defaultValueIfNotFound)
{
instance.Port = instance.Port;
}
if (configuration["Enabled"] isstring value1 && !string.IsNullOrEmpty(value1))
{
instance.Enabled = ParseBool(value1, configuration.GetSection("Enabled").Path);
}
elseif (defaultValueIfNotFound)
{
instance.Enabled = instance.Enabled;
}
if (configuration["ApiUrl"] isstring value2)
{
instance.ApiUrl = value2;
}
elseif (defaultValueIfNotFound)
{
var currentValue = instance.ApiUrl;
if (currentValue isnotnull)
{
instance.ApiUrl = currentValue;
}
}
}
///<summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>publicstaticvoidValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{
if (binderOptions?.ErrorOnUnknownConfiguration istrue)
{
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 isnotnull)
{
thrownew InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
}
}
}
publicstaticintParseInt(stringvalue, string? path)
{
try
{
returnint.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
catch (Exception exception)
{
thrownew InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception);
}
}
publicstaticboolParseBool(stringvalue, string? path)
{
try
{
returnbool.Parse(value);
}
catch (Exception exception)
{
thrownew InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception);
}
}
#endregion Core binding extensions.
}
}
Note
This generated code is subject to change based on the version of the configuration source generator.
The generated code contains the BindingExtensions class, which contains the BindCore method that performs the actual binding. The Bind_Settings method calls the BindCore method and casts the instance to the specified type.
To see the generated code, set the <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> in the project file. This ensures that the files are visible to the developer for inspection. You can also view the generated code in Visual Studio's Solution Explorer under your project's Dependencies > Analyzers > Microsoft.Extensions.Configuration.Binder.SourceGeneration node.
The source for this content can be found on GitHub, where you can also create and review issues and pull requests. For more information, see our contributor guide.
.NET feedback
.NET is an open source project. Select a link to provide feedback:
Understand and implement dependency injection in an ASP.NET Core app. Use ASP.NET Core's built-in service container to manage dependencies. Register services with the service container.
Learn the options pattern to represent groups of related settings in .NET apps. The options pattern uses classes to provide strongly-typed access to settings.