选项模式使用类来提供对相关设置组的强类型访问。 当
配置设置
由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
接口分隔原则 (ISP) 或封装
:依赖于配置设置的方案(类)仅依赖于其使用的配置设置。
关注点分离
:应用的不同部件的设置不彼此依赖或相互耦合。
选项还提供验证配置数据的机制。 有关详细信息,请参阅
选项验证
部分。
绑定分层配置
读取相关配置值的首选方法是使用选项模式。 选项模式可以通过
IOptions<TOptions>
接口实现,其中泛型类型参数
TOptions
被约束为
class
。 以后可以通过依赖关系注入来提供
IOptions<TOptions>
。 有关详细信息,请参阅
.NET 中的依赖关系注入
。
例如,从 appsettings.json 文件中读取突出显示的配置值:
"SecretKey": "Secret key value",
"TransientFaultHandlingOptions": {
"Enabled": true,
"AutoRetryDelay": "00:00:07"
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
创建以下
TransientFaultHandlingOptions
类:
public sealed class TransientFaultHandlingOptions
public bool Enabled { get; set; }
public TimeSpan AutoRetryDelay { get; set; }
在使用选项模式时,选项类:
必须是非抽象类,有一个公共无参数构造函数
包含要绑定的公共读写属性(不绑定字段)
以下代码是 Program.cs C# 文件的一部分,并且:
调用 ConfigurationBinder.Bind 将 TransientFaultHandlingOptions
类绑定到 "TransientFaultHandlingOptions"
部分。
显示 配置数据。
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ConsoleJson.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Configuration.Sources.Clear();
IHostEnvironment env = builder.Environment;
builder.Configuration
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true);
TransientFaultHandlingOptions options = new();
builder.Configuration.GetSection(nameof(TransientFaultHandlingOptions))
.Bind(options);
Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
// <Output>
// Sample output:
在前面的代码中,JSON 配置文件的 "TransientFaultHandlingOptions"
节绑定到实例 TransientFaultHandlingOptions
。 这会将 C# 对象属性与配置中的相应值水合在一起。
ConfigurationBinder.Get<T>
绑定并返回指定的类型。 使用 ConfigurationBinder.Get<T>
可能比使用 ConfigurationBinder.Bind
更方便。 下面的代码演示如何将 ConfigurationBinder.Get<T>
与 TransientFaultHandlingOptions
类配合使用:
var options =
builder.Configuration.GetSection(nameof(TransientFaultHandlingOptions))
.Get<TransientFaultHandlingOptions>();
Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");
在前面的代码中,ConfigurationBinder.Get<T>
用于获取对象 TransientFaultHandlingOptions
的实例,其属性值是从基础配置填充的。
ConfigurationBinder 类公开了几个 API,例如不被约束为 class
的 .Bind(object instance)
和 .Get<T>()
。ConfigurationBinder 使用任何一个选项接口时,都必须遵守前面提到的选项类约束。
使用选项模式时的另一种方法是绑定 "TransientFaultHandlingOptions"
部分,并将其添加到"TransientFaultHandlingOptions"
中。 在以下代码中,TransientFaultHandlingOptions
已通过 Configure 被添加到了服务容器并已绑定到了配置:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.Configure<TransientFaultHandlingOptions>(
builder.Configuration.GetSection(
key: nameof(TransientFaultHandlingOptions)));
在前面的示例中,builder
是一个 HostApplicationBuilder 实例。
key
参数是要搜索的配置部分的名称。 它不必与代表它的类型名称相匹配。 例如,你可以有一个名为 "FaultHandling"
的部分,该部分可以由 TransientFaultHandlingOptions
类来表示。 在这种情况下,可以改为将 "FaultHandling"
传递到 GetSection 函数。 在命名部分与其对应的类型相匹配时,为了方便,使用 nameof
运算符。
通过使用前面的代码,以下代码将读取位置选项:
using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
public sealed class ExampleService(IOptions<TransientFaultHandlingOptions> options)
private readonly TransientFaultHandlingOptions _options = options.Value;
public void DisplayValues()
Console.WriteLine($"TransientFaultHandlingOptions.Enabled={_options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={_options.AutoRetryDelay}");
在上面的代码中,不会读取在应用启动后对 JSON 配置文件所做的更改。 若要在应用启动后读取更改,请使用 IOptionsSnapshot 或 IOptionsMonitor 监视发生更改,并做出相应的反应。
IOptions<TOptions>?
不支持:using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
public sealed class ScopedService(IOptionsSnapshot<TransientFaultHandlingOptions> options)
private readonly TransientFaultHandlingOptions _options = options.Value;
public void DisplayValues()
Console.WriteLine($"TransientFaultHandlingOptions.Enabled={_options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={_options.AutoRetryDelay}");
以下代码注册 TransientFaultHandlingOptions
绑定的配置实例:
builder.Services
.Configure<TransientFaultHandlingOptions>(
configurationRoot.GetSection(
nameof(TransientFaultHandlingOptions)));
在前面的代码中,Configure<TOptions>
方法用于注册 TOptions
将绑定到的配置实例,并在配置更改时更新选项。
IOptionsMonitor
IOptionsMonitor
类型支持更改通知,并启用应用可能需要动态响应配置源更改的方案。 当你需要在应用启动后对配置数据中的更改做出反应时,这非常有用。 仅支持基于文件系统的配置提供程序的更改通知,例如:
Microsoft.Extensions.Configuration.Ini
Microsoft.Extensions.Configuration.Json
Microsoft.Extensions.Configuration.KeyPerFile
Microsoft.Extensions.Configuration.UserSecrets
Microsoft.Extensions.Configuration.Xml
若要使用选项监视器,选项对象的配置方式与配置节中的配置方式相同。
builder.Services
.Configure<TransientFaultHandlingOptions>(
configurationRoot.GetSection(
nameof(TransientFaultHandlingOptions)));
下面的示例使用 IOptionsMonitor<TOptions>:
using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
public sealed class MonitorService(IOptionsMonitor<TransientFaultHandlingOptions> monitor)
public void DisplayValues()
TransientFaultHandlingOptions options = monitor.CurrentValue;
Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");
在上面的代码中,已读取在应用启动后对 JSON 配置文件所做的更改。
某些文件系统(例如 Docker 容器和网络共享)可能无法可靠地发送更改通知。 在这些环境中使用 IOptionsMonitor<TOptions> 接口时,请将 DOTNET_USE_POLLING_FILE_WATCHER
环境变量设置为 1
或 true
,以便轮询文件系统的更改。 轮询更改的时间间隔是四秒,此间隔不可配置。
有关 Docker 容器的详细信息,请参阅容器化 .NET 应用。
命名选项:
当多个配置节绑定到同一属性时有用。
区分大小写。
请考虑使用以下 appsettings.json 文件:
"Features": {
"Personalize": {
"Enabled": true,
"ApiKey": "aGEgaGEgeW91IHRob3VnaHQgdGhhdCB3YXMgcmVhbGx5IHNvbWV0aGluZw=="
"WeatherStation": {
"Enabled": true,
"ApiKey": "QXJlIHlvdSBhdHRlbXB0aW5nIHRvIGhhY2sgdXM/"
下面的类用于每个节,而不是创建两个类来绑定 Features:Personalize
和 Features:WeatherStation
:
public class Features
public const string Personalize = nameof(Personalize);
public const string WeatherStation = nameof(WeatherStation);
public bool Enabled { get; set; }
public string ApiKey { get; set; }
下面的代码将配置命名选项:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// Omitted for brevity...
builder.Services.Configure<Features>(
Features.Personalize,
builder.Configuration.GetSection("Features:Personalize"));
builder.Services.Configure<Features>(
Features.WeatherStation,
builder.Configuration.GetSection("Features:WeatherStation"));
下面的代码将显示命名选项:
public sealed class Service
private readonly Features _personalizeFeature;
private readonly Features _weatherStationFeature;
public Service(IOptionsSnapshot<Features> namedOptionsAccessor)
_personalizeFeature = namedOptionsAccessor.Get(Features.Personalize);
_weatherStationFeature = namedOptionsAccessor.Get(Features.WeatherStation);
所有选项都是命名实例。 IConfigureOptions<TOptions> 实例将被视为面向 Options.DefaultName
实例,即 string.Empty
。 IConfigureNamedOptions<TOptions> 还可实现 IConfigureOptions<TOptions>。 IOptionsFactory<TOptions> 的默认实现具有适当地使用每个实例的逻辑。 null
命名选项用于面向所有命名实例,而不是某一特定命名实例。 ConfigureAll 和 PostConfigureAll 使用此约定。
OptionsBuilder API
OptionsBuilder<TOptions> 用于配置 TOptions
实例。 OptionsBuilder
简化了创建命名选项的过程,因为它只是初始 AddOptions<TOptions>(string optionsName)
调用的单个参数,而不会出现在所有后续调用中。 选项验证和接受服务依赖关系的 ConfigureOptions
重载仅可通过 OptionsBuilder
获得。
OptionsBuilder
在选项验证部分中使用。
配置选项时,可以使用依赖关系注入来访问已注册的服务,并使用它们来配置选项。 这在需要访问服务来配置选项时很有用。 在配置选项时可通过两种方式从 DI 访问服务:
将配置委托传递给 OptionsBuilder<TOptions> 上的 Configure。 OptionsBuilder<TOptions>
提供 OptionsBuilder<TOptions>
的重载,该重载允许使用最多五个服务来配置选项:
builder.Services
.AddOptions<MyOptions>("optionalName")
.Configure<ExampleService, ScopedService, MonitorService>(
(options, es, ss, ms) =>
options.Property = DoSomethingWith(es, ss, ms));
创建实现 IConfigureOptions<TOptions> 或 IConfigureNamedOptions<TOptions> 的类型,并将该类型注册为服务。
建议将配置委托传递给 Configure,因为创建服务较为复杂。 在调用 Configure 时,创建类型等效于框架执行的操作。 调用 Configure 会注册临时泛型 IConfigureNamedOptions<TOptions>,它具有接受指定的泛型服务类型的构造函数。
通过选项验证,可以验证选项值。
请考虑使用以下 appsettings.json 文件:
"MyCustomSettingsSection": {
"SiteTitle": "Amazing docs from Awesome people!",
"Scale": 10,
"VerbosityLevel": 32
下面的类绑定到 "MyCustomSettingsSection"
配置节,并应用若干 DataAnnotations
规则:
using System.ComponentModel.DataAnnotations;
namespace ConsoleJson.Example;
public sealed class SettingsOptions
public const string ConfigurationSectionName = "MyCustomSettingsSection";
[Required]
[RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
public required string SiteTitle { get; set; }
[Required]
[Range(0, 1_000,
ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public required int Scale { get; set; }
[Required]
public required int VerbosityLevel { get; set; }
在前面的 SettingsOptions
类中,ConfigurationSectionName
属性包含要绑定到的配置部分的名称。 在此方案中,选项对象提供其配置部分的名称。
配置部分名称独立于所绑定到的配置对象。 换句话说,名为 "FooBarOptions"
的配置部分可以绑定到名为 ZedOptions
的选项对象。 虽然为二者提供相同的名称很常见,但这并不是必要的,且可能会导致名称冲突。
下面的代码:
调用 AddOptions 以获取绑定到 SettingsOptions
类的 AddOptions。
调用 ValidateDataAnnotations 以使用 DataAnnotations
启用验证。
builder.Services
.AddOptions<SettingsOptions>()
.Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
.ValidateDataAnnotations();
ValidateDataAnnotations
扩展方法在 Microsoft.Extensions.Options.DataAnnotations NuGet 包中定义。
下面的代码会显示配置值或报告验证错误:
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
public sealed class ValidationService
private readonly ILogger<ValidationService> _logger;
private readonly IOptions<SettingsOptions> _config;
public ValidationService(
ILogger<ValidationService> logger,
IOptions<SettingsOptions> config)
_config = config;
_logger = logger;
SettingsOptions options = _config.Value;
catch (OptionsValidationException ex)
foreach (string failure in ex.Failures)
_logger.LogError("Validation error: {FailureMessage}", failure);
下面的代码使用委托应用更复杂的验证规则:
builder.Services
.AddOptions<SettingsOptions>()
.Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
.ValidateDataAnnotations()
.Validate(config =>
if (config.Scale != 0)
return config.VerbosityLevel > config.Scale;
return true;
}, "VerbosityLevel must be > than Scale.");
验证在运行时发生,但你可以改为通过将调用链接到 ValidateOnStart
来将其配置为在启动时发生:
builder.Services
.AddOptions<SettingsOptions>()
.Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
.ValidateDataAnnotations()
.Validate(config =>
if (config.Scale != 0)
return config.VerbosityLevel > config.Scale;
return true;
}, "VerbosityLevel must be > than Scale.")
.ValidateOnStart();
从 .NET 8 开始,可以使用备用 API AddOptionsWithValidateOnStart<TOptions>(IServiceCollection, String) 来对特定选项类型在开始时启用验证:
builder.Services
.AddOptionsWithValidateOnStart<SettingsOptions>()
.Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
.ValidateDataAnnotations()
.Validate(config =>
if (config.Scale != 0)
return config.VerbosityLevel > config.Scale;
return true;
}, "VerbosityLevel must be > than Scale.");
IValidateOptions
用于复杂验证
下面的类实现了 IValidateOptions<TOptions>:
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
sealed partial class ValidateSettingsOptions(
IConfiguration config)
: IValidateOptions<SettingsOptions>
public SettingsOptions? Settings { get; private set; } =
config.GetSection(SettingsOptions.ConfigurationSectionName)
.Get<SettingsOptions>();
public ValidateOptionsResult Validate(string? name, SettingsOptions options)
StringBuilder? failure = null;
if (!ValidationRegex().IsMatch(options.SiteTitle))
(failure ??= new()).AppendLine($"{options.SiteTitle} doesn't match RegEx");
if (options.Scale is < 0 or > 1_000)
(failure ??= new()).AppendLine($"{options.Scale} isn't within Range 0 - 1000");
if (Settings is { Scale: 0 } && Settings.VerbosityLevel <= Settings.Scale)
(failure ??= new()).AppendLine("VerbosityLevel must be > than Scale.");
return failure is not null
? ValidateOptionsResult.Fail(failure.ToString())
: ValidateOptionsResult.Success;
[GeneratedRegex("^[a-zA-Z''-'\\s]{1,40}$")]
private static partial Regex ValidationRegex();
IValidateOptions
允许将验证代码移入类中。
此示例代码依赖于 Microsoft.Extensions.Configuration.Json NuGet 包。
使用前面的代码,通过以下代码在配置服务时启用验证:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// Omitted for brevity...
builder.Services.Configure<SettingsOptions>(
builder.Configuration.GetSection(
SettingsOptions.ConfigurationSectionName));
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton
<IValidateOptions<SettingsOptions>, ValidateSettingsOptions>());
选项后期配置
使用 IPostConfigureOptions<TOptions> 设置后期配置。 后期配置在所有 IConfigureOptions<TOptions> 配置发生后运行,在需要重写配置的场景中非常有用:
builder.Services.PostConfigure<CustomOptions>(customOptions =>
customOptions.Option1 = "post_configured_option1_value";
PostConfigure 可用于对命名选项进行后期配置:
builder.Services.PostConfigure<CustomOptions>("named_options_1", customOptions =>
customOptions.Option1 = "post_configured_option1_value";
使用 PostConfigureAll 对所有配置实例进行后期配置:
builder.Services.PostConfigureAll<CustomOptions>(customOptions =>
customOptions.Option1 = "post_configured_option1_value";
.NET 中的配置
面向 .NET 库创建者的选项模式指南