- 概述
- 新增功能
- 基础知识
- 测试、调试和疑难解答
- 低版本升级迁移
ASP.NET Core 中的选项模式
作者:Luke Latham
选项模式使用类来表示相关设置的组。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
- 接口分离原则 (ISP) 或封装 – 依赖于配置设置的方案(类)仅依赖于其使用的配置设置。
- 关注点分离 – 应用的不同部件的设置不彼此依赖或相互耦合。
选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。
Package
Microsoft.Extensions.Options.ConfigurationExtensions 包已在 ASP.NET Core 应用中隐式引用。
选项接口
IOptionsMonitor<TOptions> 用于检索选项并管理 TOptions
实例的选项通知。 IOptionsMonitor<TOptions> 支持以下方案:
- 更改通知
- 命名选项
- 可重载配置
- 选择性选项失效 (IOptionsMonitorCache<TOptions>)
后期配置方案允许你在进行所有 IConfigureOptions<TOptions> 配置后设置或更改选项。
IOptionsFactory<TOptions> 负责新建选项实例。 它具有单个 Create 方法。 默认实现采用所有已注册 IConfigureOptions<TOptions> 和 IPostConfigureOptions<TOptions> 并首先运行所有配置,然后才进行后期配置。 它区分 IConfigureNamedOptions<TOptions> 和 IConfigureOptions<TOptions> 且仅调用适当的接口。
IOptionsMonitorCache<TOptions> 由 IOptionsMonitor<TOptions> 用于缓存 TOptions
实例。 IOptionsMonitorCache<TOptions> 可使监视器中的选项实例无效,以便重新计算值 (TryRemove)。 可以通过 TryAdd 手动引入值。 在应按需重新创建所有命名实例时使用 Clear 方法。
IOptionsSnapshot<TOptions> 在每次请求时应重新计算选项的方案中有用。 有关详细信息,请参阅通过 IOptionsSnapshot 重新加载配置数据部分。
IOptions<TOptions> 可用于支持选项。 但是,IOptions<TOptions> 不支持前面的 IOptionsMonitor<TOptions> 方案。 你可以在已使用 IOptions<TOptions> 接口的现有框架和库中继续使用 IOptions<TOptions>,并且不需要 IOptionsMonitor<TOptions> 提供的方案。
常规选项配置
常规选项配置已作为示例 1 在示例应用中进行了演示。
选项类必须为包含公共无参数构造函数的非抽象类。 以下类 MyOptions
具有两种属性:Option1
和 Option2
。 设置默认值为可选,但以下示例中的类构造函数设置了 Option1
的默认值。 Option2
具有通过直接初始化属性设置的默认值 (Models/MyOptions.cs ):
public class MyOptions { public MyOptions() { // Set default value. Option1 = "value1_from_ctor"; } public string Option1 { get; set; } public int Option2 { get; set; } = 5; }
MyOptions
类已通过 Configure 添加到服务容器并绑定到配置:
// Example #1: General configuration // Register the Configuration instance which MyOptions binds against. services.Configure<MyOptions>(Configuration);
以下页面模型通过 IOptionsMonitor<TOptions> 使用构造函数依赖关系注入来访问设置 (Pages/Index.cshtml.cs ):
private readonly MyOptions _options;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #1: Simple options var option1 = _options.Option1; var option2 = _options.Option2; SimpleOptions = $"option1 = {option1}, option2 = {option2}";
示例的 appsettings.json 文件指定 option1
和 option2
的值:
{ "option1": "value1_from_json", "option2": -1, "subsection": { "suboption1": "subvalue1_from_json", "suboption2": 200 }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" }
运行应用时,页面模型的 OnGet
方法返回显示选项类值的字符串:
option1 = value1_from_json, option2 = -1
备注
使用自定义 ConfigurationBuilder 从设置文件加载选项配置时,请确认基路径设置正确:
var configBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true); var config = configBuilder.Build(); services.Configure<MyOptions>(config);
通过 CreateDefaultBuilder 从设置文件加载选项配置时,不需要显式设置基路径。
通过委托配置简单选项
通过委托配置简单选项已作为示例 2 在示例应用中进行了演示。
使用委托设置选项值。 此示例应用使用 MyOptionsWithDelegateConfig
类 (Models/MyOptionsWithDelegateConfig.cs ):
public class MyOptionsWithDelegateConfig { public MyOptionsWithDelegateConfig() { // Set default value. Option1 = "value1_from_ctor"; } public string Option1 { get; set; } public int Option2 { get; set; } = 5; }
在以下代码中,已向服务容器添加第二个 IConfigureOptions<TOptions> 服务。 它通过 MyOptionsWithDelegateConfig
使用委托来配置绑定:
// Example #2: Options bound and configured by a delegate services.Configure<MyOptionsWithDelegateConfig>(myOptions => { myOptions.Option1 = "value1_configured_by_delegate"; myOptions.Option2 = 500; });
Index.cshtml.cs :
private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #2: Options configured by delegate var delegate_config_option1 = _optionsWithDelegateConfig.Option1; var delegate_config_option2 = _optionsWithDelegateConfig.Option2; SimpleOptionsWithDelegateConfig = $"delegate_option1 = {delegate_config_option1}, " + $"delegate_option2 = {delegate_config_option2}";
可添加多个配置提供程序。 配置提供程序可从 NuGet 包中获取,并按照注册的顺序应用。 有关详细信息,请参阅 ASP.NET Core 中的配置。
每次调用 Configure 都会将 IConfigureOptions<TOptions> 服务添加到服务容器。 在前面的示例中,Option1
和 Option2
的值同时在 appsettings.json 中指定,但 Option1
和 Option2
的值被配置的委托替代。
当启用多个配置服务时,指定的最后一个配置源优于其他源 ,由其设置配置值。 运行应用时,页面模型的 OnGet
方法返回显示选项类值的字符串:
delegate_option1 = value1_configured_by_delegate, delegate_option2 = 500
子选项配置
子选项配置已作为示例 3 在示例应用中进行了演示。
应用应创建适用于应用中特定方案组(类)的选项类。 需要配置值的部分应用应仅有权访问其使用的配置值。
将选项绑定到配置时,选项类型中的每个属性都将绑定到窗体 property[:sub-property:]
的配置键。 例如,MyOptions.Option1
属性将绑定到从 appsettings.json 中的 option1
属性读取的键 Option1
。
在以下代码中,已向服务容器添加第三个 IConfigureOptions<TOptions> 服务。 它将 MySubOptions
绑定到 appsettings.json 文件的 subsection
部分:
// Example #3: Suboptions // Bind options using a sub-section of the appsettings.json file. services.Configure<MySubOptions>(Configuration.GetSection("subsection"));
GetSection
方法需要 Microsoft.Extensions.Configuration 命名空间。
示例的 appsettings.json 文件定义具有 suboption1
和 suboption2
的键的 subsection
成员:
{ "option1": "value1_from_json", "option2": -1, "subsection": { "suboption1": "subvalue1_from_json", "suboption2": 200 }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" }
MySubOptions
类将属性 SubOption1
和 SubOption2
定义为保留选项值 (Models/MySubOptions.cs ):
public class MySubOptions { public MySubOptions() { // Set default values. SubOption1 = "value1_from_ctor"; SubOption2 = 5; } public string SubOption1 { get; set; } public int SubOption2 { get; set; } }
页面模型的 OnGet
方法返回包含选项值的字符串 (Pages/Index.cshtml.cs ):
private readonly MySubOptions _subOptions;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #3: Suboptions var subOption1 = _subOptions.SubOption1; var subOption2 = _subOptions.SubOption2; SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";
运行应用时,OnGet
方法返回显示子选项类值的字符串:
subOption1 = subvalue1_from_json, subOption2 = 200
选项注入
选项注入已作为示例 4 在示例应用中进行了演示。
将 IOptionsMonitor<TOptions> 注入:
- 使用
@inject
Razor 指令的 Razor 页面或 MVC 视图。 - 页面或视图模型。
示例应用中的以下示例将 IOptionsMonitor<TOptions> 注入页面模型 (Pages/Index.cshtml.cs):
private readonly MyOptions _options;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #4: Bind options directly to the page MyOptions = _options;
示例应用演示如何使用 @inject
指令注入 IOptionsMonitor<MyOptions>
:
@page @model IndexModel @using Microsoft.Extensions.Options @inject IOptionsMonitor<MyOptions> OptionsAccessor @{ ViewData["Title"] = "Options Sample"; } <h1>@ViewData["Title"]</h1>
当应用运行时,选项值显示在呈现的页面中:
通过 IOptionsSnapshot 重新加载配置数据
通过 IOptionsSnapshot<TOptions> 重新加载配置数据已作为示例 5 在示例应用中进行了演示。
通过使用 IOptionsSnapshot<TOptions>,针对请求生存期访问和缓存选项时,每个请求都会计算一次选项。
IOptionsMonitor
和 IOptionsSnapshot
之间的区别在于:
IOptionsMonitor
是一种单一示例服务,可随时检索当前选项值,这在单一实例依赖项中尤其有用。IOptionsSnapshot
是一种作用域服务,并在构造IOptionsSnapshot<T>
对象时提供选项的快照。 选项快照旨在用于暂时性和有作用域的依赖项。
以下示例演示如何在更改 appsettings.json (Pages/Index.cshtml.cs ) 后创建新的 IOptionsSnapshot<TOptions>。 在更改文件和重新加载配置之前,针对服务器的多个请求返回 appsettings.json 文件提供的常数值。
private readonly MyOptions _snapshotOptions;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #5: Snapshot options var snapshotOption1 = _snapshotOptions.Option1; var snapshotOption2 = _snapshotOptions.Option2; SnapshotOptions = $"snapshot option1 = {snapshotOption1}, " + $"snapshot option2 = {snapshotOption2}";
下图显示从 appsettings.json 文件加载的初始 option1
和 option2
值:
snapshot option1 = value1_from_json, snapshot option2 = -1
将 appsettings.json 文件中的值更改为 value1_from_json UPDATED
和 200
。 保存 appsettings.json 文件。 刷新浏览器,查看更新的选项值:
snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200
包含 IConfigureNamedOptions 的命名选项支持
包含 IConfigureNamedOptions<TOptions> 的命名选项支持已作为示例 6 在示例应用中进行了演示。
命名选项支持允许应用在命名选项配置之间进行区分。 在示例应用中,命名选项通过 OptionsServiceCollectionExtensions.Configure 进行声明,其调用扩展方法 ConfigureNamedOptions<TOptions>.Configure。 命名选项区分大小写。
// Example #6: Named options (named_options_1) // Register the ConfigurationBuilder instance which MyOptions binds against. // Specify that the options loaded from configuration are named // "named_options_1". services.Configure<MyOptions>("named_options_1", Configuration); // Example #6: Named options (named_options_2) // Specify that the options loaded from the MyOptions class are named // "named_options_2". // Use a delegate to configure option values. services.Configure<MyOptions>("named_options_2", myOptions => { myOptions.Option1 = "named_options_2_value1_from_action"; });
示例应用通过 Get (Pages/Index.cshtml.cs ) 访问命名选项:
private readonly MyOptions _named_options_1; private readonly MyOptions _named_options_2;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #6: Named options var named_options_1 = $"named_options_1: option1 = {_named_options_1.Option1}, " + $"option2 = {_named_options_1.Option2}"; var named_options_2 = $"named_options_2: option1 = {_named_options_2.Option1}, " + $"option2 = {_named_options_2.Option2}"; NamedOptions = $"{named_options_1} {named_options_2}";
运行示例应用,将返回命名选项:
named_options_1: option1 = value1_from_json, option2 = -1 named_options_2: option1 = named_options_2_value1_from_action, option2 = 5
从配置中提供从 appsettings.json 文件中加载的 named_options_1
值。 通过以下内容提供 named_options_2
值:
- 针对
Option1
的ConfigureServices
中的named_options_2
委托。 MyOptions
类提供的Option2
的默认值。
使用 ConfigureAll 方法配置所有选项
使用 ConfigureAll 方法配置所有选项实例。 以下代码将针对包含公共值的所有配置实例配置 Option1
。 将以下代码手动添加到 Startup.ConfigureServices
方法:
services.ConfigureAll<MyOptions>(myOptions => { myOptions.Option1 = "ConfigureAll replacement value"; });
添加代码后运行示例应用将产生以下结果:
named_options_1: option1 = ConfigureAll replacement value, option2 = -1 named_options_2: option1 = ConfigureAll replacement value, option2 = 5
备注
所有选项都是命名实例。 现有 IConfigureOptions<TOptions> 实例将被视为面向为 string.Empty
的 Options.DefaultName
实例。 IConfigureNamedOptions<TOptions> 还可实现 IConfigureOptions<TOptions>。 IOptionsFactory<TOptions> 的默认实现具有适当地使用每个实例的逻辑。 null
命名选项用于面向所有命名实例而不是某一特定命名实例(ConfigureAll 和 PostConfigureAll 使用此约定)。
OptionsBuilder API
OptionsBuilder<TOptions> 用于配置 TOptions
实例。 OptionsBuilder
简化了创建命名选项的过程,因为它只是初始 AddOptions<TOptions>(string optionsName)
调用的单个参数,而不会出现在所有后续调用中。 选项验证和接受服务依赖关系的 ConfigureOptions
重载仅可通过 OptionsBuilder
获得。
// Options.DefaultName = "" is used. services.AddOptions<MyOptions>().Configure(o => o.Property = "default"); services.AddOptions<MyOptions>("optionalName") .Configure(o => o.Property = "named");
使用 DI 服务配置选项
在配置选项时,可以通过以下两种方式通过依赖关系注入访问其他服务:
将配置委托传递给 OptionsBuilder<TOptions > 上的 Configure。
OptionsBuilder<TOptions>
提供 Configure 的重载,该重载允许使用最多五个服务来配置选项:services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
创建实现 IConfigureOptions<TOptions> 或 IConfigureNamedOptions<TOptions> 的你自己的类型,并将该类型注册为服务。
建议将配置委托传递给 Configure,因为创建服务较复杂。 在使用 Configure时,创建你自己的类型等效于框架为你执行的操作。 调用 Configure 会注册临时泛型 IConfigureNamedOptions<TOptions>,它具有接受指定的泛型服务类型的构造函数。
选项验证
借助选项验证,可以验证已配置的选项。 使用验证方法调用 Validate
。如果选项有效,方法返回 true
;如果无效,方法返回 false
:
// Registration services.AddOptions<MyOptions>("optionalOptionsName") .Configure(o => { }) // Configure the options .Validate(o => YourValidationShouldReturnTrueIfValid(o), "custom error"); // Consumption var monitor = services.BuildServiceProvider() .GetService<IOptionsMonitor<MyOptions>>(); try { var options = monitor.Get("optionalOptionsName"); } catch (OptionsValidationException e) { // e.OptionsName returns "optionalOptionsName" // e.OptionsType returns typeof(MyOptions) // e.Failures returns a list of errors, which would contain // "custom error" }
上面的示例将命名选项实例设置为 optionalOptionsName
。 默认选项实例为 Options.DefaultName
。
选项验证在选项实例创建后运行。 系统保证在选项实例首次获得访问时通过验证。
重要
选项验证无法防止在创建选项实例后发生选项修改。 例如,在首次访问选项时按请求创建并验证 IOptionsSnapshot
选项。 对于同一个请求,在后续访问尝试时不会再次验证 IOptionsSnapshot
选项 。
Validate
方法接受 Func<TOptions, bool>
。 若要完全自定义验证,请实现 IValidateOptions<TOptions>
,它支持:
- 验证多种类型的选项:
class ValidateTwo : IValidateOptions<Option1>, IValidationOptions<Option2>
- 验证取决于其他选项类型:
public DependsOnAnotherOptionValidator(IOptionsMonitor<AnotherOption> options)
IValidateOptions
验证:
- 特定的命名选项实例。
- 所有选项(如果
name
为null
的话)。
通过接口实现返回 ValidateOptionsResult
:
public interface IValidateOptions<TOptions> where TOptions : class { ValidateOptionsResult Validate(string name, TOptions options); }
通过调用 OptionsBuilder<TOptions>
上的 ValidateDataAnnotations 方法,可以从 Microsoft.Extensions.Options.DataAnnotations 包中获得基于数据注释的验证。 Microsoft.Extensions.Options.DataAnnotations
已在 ASP.NET Core 应用中隐式引用。
using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.DependencyInjection; private class AnnotatedOptions { [Required] public string Required { get; set; } [StringLength(5, ErrorMessage = "Too long.")] public string StringLength { get; set; } [Range(-5, 5, ErrorMessage = "Out of range.")] public int IntRange { get; set; } } [Fact] public void CanValidateDataAnnotations() { var services = new ServiceCollection(); services.AddOptions<AnnotatedOptions>() .Configure(o => { o.StringLength = "111111"; o.IntRange = 10; o.Custom = "nowhere"; }) .ValidateDataAnnotations(); var sp = services.BuildServiceProvider(); var error = Assert.Throws<OptionsValidationException>(() => sp.GetRequiredService<IOptionsMonitor<AnnotatedOptions>>().CurrentValue); ValidateFailure<AnnotatedOptions>(error, Options.DefaultName, 1, "DataAnnotation validation failed for members Required " + "with the error 'The Required field is required.'.", "DataAnnotation validation failed for members StringLength " + "with the error 'Too long.'.", "DataAnnotation validation failed for members IntRange " + "with the error 'Out of range.'."); }
设计团队正在考虑为未来版本提供预先验证(在启动时快速失败)。
选项后期配置
使用 IPostConfigureOptions<TOptions> 设置后期配置。 进行所有 IConfigureOptions<TOptions> 配置后运行后期配置:
services.PostConfigure<MyOptions>(myOptions => { myOptions.Option1 = "post_configured_option1_value"; });
PostConfigure 可用于对命名选项进行后期配置:
services.PostConfigure<MyOptions>("named_options_1", myOptions => { myOptions.Option1 = "post_configured_option1_value"; });
使用 PostConfigureAll 对所有配置实例进行后期配置:
services.PostConfigureAll<MyOptions>(myOptions => { myOptions.Option1 = "post_configured_option1_value"; });
在启动期间访问选项
IOptions<TOptions> 和 IOptionsMonitor<TOptions> 可用于 Startup.Configure
中,因为在 Configure
方法执行之前已生成服务。
public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor) { var option1 = optionsAccessor.CurrentValue.Option1; }
不使用 Startup.ConfigureServices
中的 IOptions<TOptions> 或 IOptionsMonitor<TOptions>。 由于服务注册的顺序,可能存在不一致的选项状态。
选项模式使用类来表示相关设置的组。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
- 接口分离原则 (ISP) 或封装 – 依赖于配置设置的方案(类)仅依赖于其使用的配置设置。
- 关注点分离 – 应用的不同部件的设置不彼此依赖或相互耦合。
选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。
先决条件
引用 Microsoft.AspNetCore.App 元包或将包引用添加到 Microsoft.Extensions.Options.ConfigurationExtensions 包。
选项接口
IOptionsMonitor<TOptions> 用于检索选项并管理 TOptions
实例的选项通知。 IOptionsMonitor<TOptions> 支持以下方案:
- 更改通知
- 命名选项
- 可重载配置
- 选择性选项失效 (IOptionsMonitorCache<TOptions>)
后期配置方案允许你在进行所有 IConfigureOptions<TOptions> 配置后设置或更改选项。
IOptionsFactory<TOptions> 负责新建选项实例。 它具有单个 Create 方法。 默认实现采用所有已注册 IConfigureOptions<TOptions> 和 IPostConfigureOptions<TOptions> 并首先运行所有配置,然后才进行后期配置。 它区分 IConfigureNamedOptions<TOptions> 和 IConfigureOptions<TOptions> 且仅调用适当的接口。
IOptionsMonitorCache<TOptions> 由 IOptionsMonitor<TOptions> 用于缓存 TOptions
实例。 IOptionsMonitorCache<TOptions> 可使监视器中的选项实例无效,以便重新计算值 (TryRemove)。 可以通过 TryAdd 手动引入值。 在应按需重新创建所有命名实例时使用 Clear 方法。
IOptionsSnapshot<TOptions> 在每次请求时应重新计算选项的方案中有用。 有关详细信息,请参阅通过 IOptionsSnapshot 重新加载配置数据部分。
IOptions<TOptions> 可用于支持选项。 但是,IOptions<TOptions> 不支持前面的 IOptionsMonitor<TOptions> 方案。 你可以在已使用 IOptions<TOptions> 接口的现有框架和库中继续使用 IOptions<TOptions>,并且不需要 IOptionsMonitor<TOptions> 提供的方案。
常规选项配置
常规选项配置已作为示例 1 在示例应用中进行了演示。
选项类必须为包含公共无参数构造函数的非抽象类。 以下类 MyOptions
具有两种属性:Option1
和 Option2
。 设置默认值为可选,但以下示例中的类构造函数设置了 Option1
的默认值。 Option2
具有通过直接初始化属性设置的默认值 (Models/MyOptions.cs ):
public class MyOptions { public MyOptions() { // Set default value. Option1 = "value1_from_ctor"; } public string Option1 { get; set; } public int Option2 { get; set; } = 5; }
MyOptions
类已通过 Configure 添加到服务容器并绑定到配置:
// Example #1: General configuration // Register the Configuration instance which MyOptions binds against. services.Configure<MyOptions>(Configuration);
以下页面模型通过 IOptionsMonitor<TOptions> 使用构造函数依赖关系注入来访问设置 (Pages/Index.cshtml.cs ):
private readonly MyOptions _options;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #1: Simple options var option1 = _options.Option1; var option2 = _options.Option2; SimpleOptions = $"option1 = {option1}, option2 = {option2}";
示例的 appsettings.json 文件指定 option1
和 option2
的值:
{ "option1": "value1_from_json", "option2": -1, "subsection": { "suboption1": "subvalue1_from_json", "suboption2": 200 }, "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*" }
运行应用时,页面模型的 OnGet
方法返回显示选项类值的字符串:
option1 = value1_from_json, option2 = -1
备注
使用自定义 ConfigurationBuilder 从设置文件加载选项配置时,请确认基路径设置正确:
var configBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true); var config = configBuilder.Build(); services.Configure<MyOptions>(config);
通过 CreateDefaultBuilder 从设置文件加载选项配置时,不需要显式设置基路径。
通过委托配置简单选项
通过委托配置简单选项已作为示例 2 在示例应用中进行了演示。
使用委托设置选项值。 此示例应用使用 MyOptionsWithDelegateConfig
类 (Models/MyOptionsWithDelegateConfig.cs ):
public class MyOptionsWithDelegateConfig { public MyOptionsWithDelegateConfig() { // Set default value. Option1 = "value1_from_ctor"; } public string Option1 { get; set; } public int Option2 { get; set; } = 5; }
在以下代码中,已向服务容器添加第二个 IConfigureOptions<TOptions> 服务。 它通过 MyOptionsWithDelegateConfig
使用委托来配置绑定:
// Example #2: Options bound and configured by a delegate services.Configure<MyOptionsWithDelegateConfig>(myOptions => { myOptions.Option1 = "value1_configured_by_delegate"; myOptions.Option2 = 500; });
Index.cshtml.cs :
private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #2: Options configured by delegate var delegate_config_option1 = _optionsWithDelegateConfig.Option1; var delegate_config_option2 = _optionsWithDelegateConfig.Option2; SimpleOptionsWithDelegateConfig = $"delegate_option1 = {delegate_config_option1}, " + $"delegate_option2 = {delegate_config_option2}";
可添加多个配置提供程序。 配置提供程序可从 NuGet 包中获取,并按照注册的顺序应用。 有关详细信息,请参阅 ASP.NET Core 中的配置。
每次调用 Configure 都会将 IConfigureOptions<TOptions> 服务添加到服务容器。 在前面的示例中,Option1
和 Option2
的值同时在 appsettings.json 中指定,但 Option1
和 Option2
的值被配置的委托替代。
当启用多个配置服务时,指定的最后一个配置源优于其他源 ,由其设置配置值。 运行应用时,页面模型的 OnGet
方法返回显示选项类值的字符串:
delegate_option1 = value1_configured_by_delegate, delegate_option2 = 500
子选项配置
子选项配置已作为示例 3 在示例应用中进行了演示。
应用应创建适用于应用中特定方案组(类)的选项类。 需要配置值的部分应用应仅有权访问其使用的配置值。
将选项绑定到配置时,选项类型中的每个属性都将绑定到窗体 property[:sub-property:]
的配置键。 例如,MyOptions.Option1
属性将绑定到从 appsettings.json 中的 option1
属性读取的键 Option1
。
在以下代码中,已向服务容器添加第三个 IConfigureOptions<TOptions> 服务。 它将 MySubOptions
绑定到 appsettings.json 文件的 subsection
部分:
// Example #3: Suboptions // Bind options using a sub-section of the appsettings.json file. services.Configure<MySubOptions>(Configuration.GetSection("subsection"));
GetSection
方法需要 Microsoft.Extensions.Configuration 命名空间。
示例的 appsettings.json 文件定义具有 suboption1
和 suboption2
的键的 subsection
成员:
{ "option1": "value1_from_json", "option2": -1, "subsection": { "suboption1": "subvalue1_from_json", "suboption2": 200 }, "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*" }
MySubOptions
类将属性 SubOption1
和 SubOption2
定义为保留选项值 (Models/MySubOptions.cs ):
public class MySubOptions { public MySubOptions() { // Set default values. SubOption1 = "value1_from_ctor"; SubOption2 = 5; } public string SubOption1 { get; set; } public int SubOption2 { get; set; } }
页面模型的 OnGet
方法返回包含选项值的字符串 (Pages/Index.cshtml.cs ):
private readonly MySubOptions _subOptions;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #3: Suboptions var subOption1 = _subOptions.SubOption1; var subOption2 = _subOptions.SubOption2; SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";
运行应用时,OnGet
方法返回显示子选项类值的字符串:
subOption1 = subvalue1_from_json, subOption2 = 200
选项注入
选项注入已作为示例 4 在示例应用中进行了演示。
将 IOptionsMonitor<TOptions> 注入:
- 使用
@inject
Razor 指令的 Razor 页面或 MVC 视图。 - 页面或视图模型。
示例应用中的以下示例将 IOptionsMonitor<TOptions> 注入页面模型 (Pages/Index.cshtml.cs):
private readonly MyOptions _options;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #4: Bind options directly to the page MyOptions = _options;
示例应用演示如何使用 @inject
指令注入 IOptionsMonitor<MyOptions>
:
@page @model IndexModel @using Microsoft.Extensions.Options @inject IOptionsMonitor<MyOptions> OptionsAccessor @{ ViewData["Title"] = "Options Sample"; } <h1>@ViewData["Title"]</h1>
当应用运行时,选项值显示在呈现的页面中:
通过 IOptionsSnapshot 重新加载配置数据
通过 IOptionsSnapshot<TOptions> 重新加载配置数据已作为示例 5 在示例应用中进行了演示。
通过使用 IOptionsSnapshot<TOptions>,针对请求生存期访问和缓存选项时,每个请求都会计算一次选项。
IOptionsMonitor
和 IOptionsSnapshot
之间的区别在于:
IOptionsMonitor
是一种单一示例服务,可随时检索当前选项值,这在单一实例依赖项中尤其有用。IOptionsSnapshot
是一种作用域服务,并在构造IOptionsSnapshot<T>
对象时提供选项的快照。 选项快照旨在用于暂时性和有作用域的依赖项。
以下示例演示如何在更改 appsettings.json (Pages/Index.cshtml.cs ) 后创建新的 IOptionsSnapshot<TOptions>。 在更改文件和重新加载配置之前,针对服务器的多个请求返回 appsettings.json 文件提供的常数值。
private readonly MyOptions _snapshotOptions;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #5: Snapshot options var snapshotOption1 = _snapshotOptions.Option1; var snapshotOption2 = _snapshotOptions.Option2; SnapshotOptions = $"snapshot option1 = {snapshotOption1}, " + $"snapshot option2 = {snapshotOption2}";
下图显示从 appsettings.json 文件加载的初始 option1
和 option2
值:
snapshot option1 = value1_from_json, snapshot option2 = -1
将 appsettings.json 文件中的值更改为 value1_from_json UPDATED
和 200
。 保存 appsettings.json 文件。 刷新浏览器,查看更新的选项值:
snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200
包含 IConfigureNamedOptions 的命名选项支持
包含 IConfigureNamedOptions<TOptions> 的命名选项支持已作为示例 6 在示例应用中进行了演示。
命名选项支持允许应用在命名选项配置之间进行区分。 在示例应用中,命名选项通过 OptionsServiceCollectionExtensions.Configure 进行声明,其调用扩展方法 ConfigureNamedOptions<TOptions>.Configure。 命名选项区分大小写。
// Example #6: Named options (named_options_1) // Register the ConfigurationBuilder instance which MyOptions binds against. // Specify that the options loaded from configuration are named // "named_options_1". services.Configure<MyOptions>("named_options_1", Configuration); // Example #6: Named options (named_options_2) // Specify that the options loaded from the MyOptions class are named // "named_options_2". // Use a delegate to configure option values. services.Configure<MyOptions>("named_options_2", myOptions => { myOptions.Option1 = "named_options_2_value1_from_action"; });
示例应用通过 Get (Pages/Index.cshtml.cs ) 访问命名选项:
private readonly MyOptions _named_options_1; private readonly MyOptions _named_options_2;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #6: Named options var named_options_1 = $"named_options_1: option1 = {_named_options_1.Option1}, " + $"option2 = {_named_options_1.Option2}"; var named_options_2 = $"named_options_2: option1 = {_named_options_2.Option1}, " + $"option2 = {_named_options_2.Option2}"; NamedOptions = $"{named_options_1} {named_options_2}";
运行示例应用,将返回命名选项:
named_options_1: option1 = value1_from_json, option2 = -1 named_options_2: option1 = named_options_2_value1_from_action, option2 = 5
从配置中提供从 appsettings.json 文件中加载的 named_options_1
值。 通过以下内容提供 named_options_2
值:
- 针对
Option1
的ConfigureServices
中的named_options_2
委托。 MyOptions
类提供的Option2
的默认值。
使用 ConfigureAll 方法配置所有选项
使用 ConfigureAll 方法配置所有选项实例。 以下代码将针对包含公共值的所有配置实例配置 Option1
。 将以下代码手动添加到 Startup.ConfigureServices
方法:
services.ConfigureAll<MyOptions>(myOptions => { myOptions.Option1 = "ConfigureAll replacement value"; });
添加代码后运行示例应用将产生以下结果:
named_options_1: option1 = ConfigureAll replacement value, option2 = -1 named_options_2: option1 = ConfigureAll replacement value, option2 = 5
备注
所有选项都是命名实例。 现有 IConfigureOptions<TOptions> 实例将被视为面向为 string.Empty
的 Options.DefaultName
实例。 IConfigureNamedOptions<TOptions> 还可实现 IConfigureOptions<TOptions>。 IOptionsFactory<TOptions> 的默认实现具有适当地使用每个实例的逻辑。 null
命名选项用于面向所有命名实例而不是某一特定命名实例(ConfigureAll 和 PostConfigureAll 使用此约定)。
OptionsBuilder API
OptionsBuilder<TOptions> 用于配置 TOptions
实例。 OptionsBuilder
简化了创建命名选项的过程,因为它只是初始 AddOptions<TOptions>(string optionsName)
调用的单个参数,而不会出现在所有后续调用中。 选项验证和接受服务依赖关系的 ConfigureOptions
重载仅可通过 OptionsBuilder
获得。
// Options.DefaultName = "" is used. services.AddOptions<MyOptions>().Configure(o => o.Property = "default"); services.AddOptions<MyOptions>("optionalName") .Configure(o => o.Property = "named");
使用 DI 服务配置选项
在配置选项时,可以通过以下两种方式通过依赖关系注入访问其他服务:
将配置委托传递给 OptionsBuilder<TOptions > 上的 Configure。 OptionsBuilder<TOptions> 提供 Configure 的重载,该重载允许使用最多五个服务来配置选项:
services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
创建实现 IConfigureOptions<TOptions> 或 IConfigureNamedOptions<TOptions> 的你自己的类型,并将该类型注册为服务。
建议将配置委托传递给 Configure,因为创建服务较复杂。 在使用 Configure时,创建你自己的类型等效于框架为你执行的操作。 调用 Configure 会注册临时泛型 IConfigureNamedOptions<TOptions>,它具有接受指定的泛型服务类型的构造函数。
选项验证
借助选项验证,可以验证已配置的选项。 使用验证方法调用 Validate
。如果选项有效,方法返回 true
;如果无效,方法返回 false
:
// Registration services.AddOptions<MyOptions>("optionalOptionsName") .Configure(o => { }) // Configure the options .Validate(o => YourValidationShouldReturnTrueIfValid(o), "custom error"); // Consumption var monitor = services.BuildServiceProvider() .GetService<IOptionsMonitor<MyOptions>>(); try { var options = monitor.Get("optionalOptionsName"); } catch (OptionsValidationException e) { // e.OptionsName returns "optionalOptionsName" // e.OptionsType returns typeof(MyOptions) // e.Failures returns a list of errors, which would contain // "custom error" }
上面的示例将命名选项实例设置为 optionalOptionsName
。 默认选项实例为 Options.DefaultName
。
选项验证在选项实例创建后运行。 系统保证在选项实例首次获得访问时通过验证。
重要
选项验证无法防止在创建选项实例后发生选项修改。 例如,在首次访问选项时按请求创建并验证 IOptionsSnapshot
选项。 对于同一个请求,在后续访问尝试时不会再次验证 IOptionsSnapshot
选项 。
Validate
方法接受 Func<TOptions, bool>
。 若要完全自定义验证,请实现 IValidateOptions<TOptions>
,它支持:
- 验证多种类型的选项:
class ValidateTwo : IValidateOptions<Option1>, IValidationOptions<Option2>
- 验证取决于其他选项类型:
public DependsOnAnotherOptionValidator(IOptionsMonitor<AnotherOption> options)
IValidateOptions
验证:
- 特定的命名选项实例。
- 所有选项(如果
name
为null
的话)。
通过接口实现返回 ValidateOptionsResult
:
public interface IValidateOptions<TOptions> where TOptions : class { ValidateOptionsResult Validate(string name, TOptions options); }
通过调用 OptionsBuilder<TOptions>
上的 ValidateDataAnnotations 方法,可以从 Microsoft.Extensions.Options.DataAnnotations 包中获得基于数据注释的验证。 Microsoft.Extensions.Options.DataAnnotations
包含在 Microsoft.AspNetCore.App 元包中。
using Microsoft.Extensions.DependencyInjection; private class AnnotatedOptions { [Required] public string Required { get; set; } [StringLength(5, ErrorMessage = "Too long.")] public string StringLength { get; set; } [Range(-5, 5, ErrorMessage = "Out of range.")] public int IntRange { get; set; } } [Fact] public void CanValidateDataAnnotations() { var services = new ServiceCollection(); services.AddOptions<AnnotatedOptions>() .Configure(o => { o.StringLength = "111111"; o.IntRange = 10; o.Custom = "nowhere"; }) .ValidateDataAnnotations(); var sp = services.BuildServiceProvider(); var error = Assert.Throws<OptionsValidationException>(() => sp.GetRequiredService<IOptionsMonitor<AnnotatedOptions>>().CurrentValue); ValidateFailure<AnnotatedOptions>(error, Options.DefaultName, 1, "DataAnnotation validation failed for members Required " + "with the error 'The Required field is required.'.", "DataAnnotation validation failed for members StringLength " + "with the error 'Too long.'.", "DataAnnotation validation failed for members IntRange " + "with the error 'Out of range.'."); }
设计团队正在考虑为未来版本提供预先验证(在启动时快速失败)。
选项后期配置
使用 IPostConfigureOptions<TOptions> 设置后期配置。 进行所有 IConfigureOptions<TOptions> 配置后运行后期配置:
services.PostConfigure<MyOptions>(myOptions => { myOptions.Option1 = "post_configured_option1_value"; });
PostConfigure 可用于对命名选项进行后期配置:
services.PostConfigure<MyOptions>("named_options_1", myOptions => { myOptions.Option1 = "post_configured_option1_value"; });
使用 PostConfigureAll 对所有配置实例进行后期配置:
services.PostConfigureAll<MyOptions>(myOptions => { myOptions.Option1 = "post_configured_option1_value"; });
在启动期间访问选项
IOptions<TOptions> 和 IOptionsMonitor<TOptions> 可用于 Startup.Configure
中,因为在 Configure
方法执行之前已生成服务。
public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor) { var option1 = optionsAccessor.CurrentValue.Option1; }
不使用 Startup.ConfigureServices
中的 IOptions<TOptions> 或 IOptionsMonitor<TOptions>。 由于服务注册的顺序,可能存在不一致的选项状态。
选项模式使用类来表示相关设置的组。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
- 接口分离原则 (ISP) 或封装 – 依赖于配置设置的方案(类)仅依赖于其使用的配置设置。
- 关注点分离 – 应用的不同部件的设置不彼此依赖或相互耦合。
选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。
先决条件
引用 Microsoft.AspNetCore.App 元包或将包引用添加到 Microsoft.Extensions.Options.ConfigurationExtensions 包。
选项接口
IOptionsMonitor<TOptions> 用于检索选项并管理 TOptions
实例的选项通知。 IOptionsMonitor<TOptions> 支持以下方案:
- 更改通知
- 命名选项
- 可重载配置
- 选择性选项失效 (IOptionsMonitorCache<TOptions>)
后期配置方案允许你在进行所有 IConfigureOptions<TOptions> 配置后设置或更改选项。
IOptionsFactory<TOptions> 负责新建选项实例。 它具有单个 Create 方法。 默认实现采用所有已注册 IConfigureOptions<TOptions> 和 IPostConfigureOptions<TOptions> 并首先运行所有配置,然后才进行后期配置。 它区分 IConfigureNamedOptions<TOptions> 和 IConfigureOptions<TOptions> 且仅调用适当的接口。
IOptionsMonitorCache<TOptions> 由 IOptionsMonitor<TOptions> 用于缓存 TOptions
实例。 IOptionsMonitorCache<TOptions> 可使监视器中的选项实例无效,以便重新计算值 (TryRemove)。 可以通过 TryAdd 手动引入值。 在应按需重新创建所有命名实例时使用 Clear 方法。
IOptionsSnapshot<TOptions> 在每次请求时应重新计算选项的方案中有用。 有关详细信息,请参阅通过 IOptionsSnapshot 重新加载配置数据部分。
IOptions<TOptions> 可用于支持选项。 但是,IOptions<TOptions> 不支持前面的 IOptionsMonitor<TOptions> 方案。 你可以在已使用 IOptions<TOptions> 接口的现有框架和库中继续使用 IOptions<TOptions>,并且不需要 IOptionsMonitor<TOptions> 提供的方案。
常规选项配置
常规选项配置已作为示例 1 在示例应用中进行了演示。
选项类必须为包含公共无参数构造函数的非抽象类。 以下类 MyOptions
具有两种属性:Option1
和 Option2
。 设置默认值为可选,但以下示例中的类构造函数设置了 Option1
的默认值。 Option2
具有通过直接初始化属性设置的默认值 (Models/MyOptions.cs ):
public class MyOptions { public MyOptions() { // Set default value. Option1 = "value1_from_ctor"; } public string Option1 { get; set; } public int Option2 { get; set; } = 5; }
MyOptions
类已通过 Configure 添加到服务容器并绑定到配置:
// Example #1: General configuration // Register the Configuration instance which MyOptions binds against. services.Configure<MyOptions>(Configuration);
以下页面模型通过 IOptionsMonitor<TOptions> 使用构造函数依赖关系注入来访问设置 (Pages/Index.cshtml.cs ):
private readonly MyOptions _options;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #1: Simple options var option1 = _options.Option1; var option2 = _options.Option2; SimpleOptions = $"option1 = {option1}, option2 = {option2}";
示例的 appsettings.json 文件指定 option1
和 option2
的值:
{ "option1": "value1_from_json", "option2": -1, "subsection": { "suboption1": "subvalue1_from_json", "suboption2": 200 }, "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*" }
运行应用时,页面模型的 OnGet
方法返回显示选项类值的字符串:
option1 = value1_from_json, option2 = -1
备注
使用自定义 ConfigurationBuilder 从设置文件加载选项配置时,请确认基路径设置正确:
var configBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true); var config = configBuilder.Build(); services.Configure<MyOptions>(config);
通过 CreateDefaultBuilder 从设置文件加载选项配置时,不需要显式设置基路径。
通过委托配置简单选项
通过委托配置简单选项已作为示例 2 在示例应用中进行了演示。
使用委托设置选项值。 此示例应用使用 MyOptionsWithDelegateConfig
类 (Models/MyOptionsWithDelegateConfig.cs ):
public class MyOptionsWithDelegateConfig { public MyOptionsWithDelegateConfig() { // Set default value. Option1 = "value1_from_ctor"; } public string Option1 { get; set; } public int Option2 { get; set; } = 5; }
在以下代码中,已向服务容器添加第二个 IConfigureOptions<TOptions> 服务。 它通过 MyOptionsWithDelegateConfig
使用委托来配置绑定:
// Example #2: Options bound and configured by a delegate services.Configure<MyOptionsWithDelegateConfig>(myOptions => { myOptions.Option1 = "value1_configured_by_delegate"; myOptions.Option2 = 500; });
Index.cshtml.cs :
private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #2: Options configured by delegate var delegate_config_option1 = _optionsWithDelegateConfig.Option1; var delegate_config_option2 = _optionsWithDelegateConfig.Option2; SimpleOptionsWithDelegateConfig = $"delegate_option1 = {delegate_config_option1}, " + $"delegate_option2 = {delegate_config_option2}";
可添加多个配置提供程序。 配置提供程序可从 NuGet 包中获取,并按照注册的顺序应用。 有关详细信息,请参阅 ASP.NET Core 中的配置。
每次调用 Configure 都会将 IConfigureOptions<TOptions> 服务添加到服务容器。 在前面的示例中,Option1
和 Option2
的值同时在 appsettings.json 中指定,但 Option1
和 Option2
的值被配置的委托替代。
当启用多个配置服务时,指定的最后一个配置源优于其他源 ,由其设置配置值。 运行应用时,页面模型的 OnGet
方法返回显示选项类值的字符串:
delegate_option1 = value1_configured_by_delegate, delegate_option2 = 500
子选项配置
子选项配置已作为示例 3 在示例应用中进行了演示。
应用应创建适用于应用中特定方案组(类)的选项类。 需要配置值的部分应用应仅有权访问其使用的配置值。
将选项绑定到配置时,选项类型中的每个属性都将绑定到窗体 property[:sub-property:]
的配置键。 例如,MyOptions.Option1
属性将绑定到从 appsettings.json 中的 option1
属性读取的键 Option1
。
在以下代码中,已向服务容器添加第三个 IConfigureOptions<TOptions> 服务。 它将 MySubOptions
绑定到 appsettings.json 文件的 subsection
部分:
// Example #3: Suboptions // Bind options using a sub-section of the appsettings.json file. services.Configure<MySubOptions>(Configuration.GetSection("subsection"));
GetSection
方法需要 Microsoft.Extensions.Configuration 命名空间。
示例的 appsettings.json 文件定义具有 suboption1
和 suboption2
的键的 subsection
成员:
{ "option1": "value1_from_json", "option2": -1, "subsection": { "suboption1": "subvalue1_from_json", "suboption2": 200 }, "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*" }
MySubOptions
类将属性 SubOption1
和 SubOption2
定义为保留选项值 (Models/MySubOptions.cs ):
public class MySubOptions { public MySubOptions() { // Set default values. SubOption1 = "value1_from_ctor"; SubOption2 = 5; } public string SubOption1 { get; set; } public int SubOption2 { get; set; } }
页面模型的 OnGet
方法返回包含选项值的字符串 (Pages/Index.cshtml.cs ):
private readonly MySubOptions _subOptions;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #3: Suboptions var subOption1 = _subOptions.SubOption1; var subOption2 = _subOptions.SubOption2; SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";
运行应用时,OnGet
方法返回显示子选项类值的字符串:
subOption1 = subvalue1_from_json, subOption2 = 200
视图模型或通过直接视图注入提供的选项
视图模型或通过直接视图注入提供的选项已作为示例 4 在示例应用中进行了演示。
可在视图模型中或通过将 IOptionsMonitor<TOptions> 直接注入到视图 (Pages/Index.cshtml.cs ) 来提供选项:
private readonly MyOptions _options;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #4: Bind options directly to the page MyOptions = _options;
示例应用演示如何使用 @inject
指令注入 IOptionsMonitor<MyOptions>
:
@page @model IndexModel @using Microsoft.Extensions.Options @inject IOptionsMonitor<MyOptions> OptionsAccessor @{ ViewData["Title"] = "Options Sample"; } <h1>@ViewData["Title"]</h1>
当应用运行时,选项值显示在呈现的页面中:
通过 IOptionsSnapshot 重新加载配置数据
通过 IOptionsSnapshot<TOptions> 重新加载配置数据已作为示例 5 在示例应用中进行了演示。
IOptionsSnapshot<TOptions> 支持包含最小处理开销的重新加载选项。
针对请求生存期访问和缓存选项时,每个请求只能计算一次选项。
以下示例演示如何在更改 appsettings.json (Pages/Index.cshtml.cs ) 后创建新的 IOptionsSnapshot<TOptions>。 在更改文件和重新加载配置之前,针对服务器的多个请求返回 appsettings.json 文件提供的常数值。
private readonly MyOptions _snapshotOptions;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #5: Snapshot options var snapshotOption1 = _snapshotOptions.Option1; var snapshotOption2 = _snapshotOptions.Option2; SnapshotOptions = $"snapshot option1 = {snapshotOption1}, " + $"snapshot option2 = {snapshotOption2}";
下图显示从 appsettings.json 文件加载的初始 option1
和 option2
值:
snapshot option1 = value1_from_json, snapshot option2 = -1
将 appsettings.json 文件中的值更改为 value1_from_json UPDATED
和 200
。 保存 appsettings.json 文件。 刷新浏览器,查看更新的选项值:
snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200
包含 IConfigureNamedOptions 的命名选项支持
包含 IConfigureNamedOptions<TOptions> 的命名选项支持已作为示例 6 在示例应用中进行了演示。
命名选项支持允许应用在命名选项配置之间进行区分。 在示例应用中,命名选项通过 OptionsServiceCollectionExtensions.Configure 进行声明,其调用扩展方法 ConfigureNamedOptions<TOptions>.Configure。 命名选项区分大小写。
// Example #6: Named options (named_options_1) // Register the ConfigurationBuilder instance which MyOptions binds against. // Specify that the options loaded from configuration are named // "named_options_1". services.Configure<MyOptions>("named_options_1", Configuration); // Example #6: Named options (named_options_2) // Specify that the options loaded from the MyOptions class are named // "named_options_2". // Use a delegate to configure option values. services.Configure<MyOptions>("named_options_2", myOptions => { myOptions.Option1 = "named_options_2_value1_from_action"; });
示例应用通过 Get (Pages/Index.cshtml.cs ) 访问命名选项:
private readonly MyOptions _named_options_1; private readonly MyOptions _named_options_2;
public IndexModel( IOptionsMonitor<MyOptions> optionsAccessor, IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, IOptionsMonitor<MySubOptions> subOptionsAccessor, IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, IOptionsSnapshot<MyOptions> namedOptionsAccessor) { _options = optionsAccessor.CurrentValue; _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue; _subOptions = subOptionsAccessor.CurrentValue; _snapshotOptions = snapshotOptionsAccessor.Value; _named_options_1 = namedOptionsAccessor.Get("named_options_1"); _named_options_2 = namedOptionsAccessor.Get("named_options_2"); }
// Example #6: Named options var named_options_1 = $"named_options_1: option1 = {_named_options_1.Option1}, " + $"option2 = {_named_options_1.Option2}"; var named_options_2 = $"named_options_2: option1 = {_named_options_2.Option1}, " + $"option2 = {_named_options_2.Option2}"; NamedOptions = $"{named_options_1} {named_options_2}";
运行示例应用,将返回命名选项:
named_options_1: option1 = value1_from_json, option2 = -1 named_options_2: option1 = named_options_2_value1_from_action, option2 = 5
从配置中提供从 appsettings.json 文件中加载的 named_options_1
值。 通过以下内容提供 named_options_2
值:
- 针对
Option1
的ConfigureServices
中的named_options_2
委托。 MyOptions
类提供的Option2
的默认值。
使用 ConfigureAll 方法配置所有选项
使用 ConfigureAll 方法配置所有选项实例。 以下代码将针对包含公共值的所有配置实例配置 Option1
。 将以下代码手动添加到 Startup.ConfigureServices
方法:
services.ConfigureAll<MyOptions>(myOptions => { myOptions.Option1 = "ConfigureAll replacement value"; });
添加代码后运行示例应用将产生以下结果:
named_options_1: option1 = ConfigureAll replacement value, option2 = -1 named_options_2: option1 = ConfigureAll replacement value, option2 = 5
备注
所有选项都是命名实例。 现有 IConfigureOptions<TOptions> 实例将被视为面向为 string.Empty
的 Options.DefaultName
实例。 IConfigureNamedOptions<TOptions> 还可实现 IConfigureOptions<TOptions>。 IOptionsFactory<TOptions> 的默认实现具有适当地使用每个实例的逻辑。 null
命名选项用于面向所有命名实例而不是某一特定命名实例(ConfigureAll 和 PostConfigureAll 使用此约定)。
OptionsBuilder API
OptionsBuilder<TOptions> 用于配置 TOptions
实例。 OptionsBuilder
简化了创建命名选项的过程,因为它只是初始 AddOptions<TOptions>(string optionsName)
调用的单个参数,而不会出现在所有后续调用中。 选项验证和接受服务依赖关系的 ConfigureOptions
重载仅可通过 OptionsBuilder
获得。
// Options.DefaultName = "" is used. services.AddOptions<MyOptions>().Configure(o => o.Property = "default"); services.AddOptions<MyOptions>("optionalName") .Configure(o => o.Property = "named");
使用 DI 服务配置选项
在配置选项时,可以通过以下两种方式通过依赖关系注入访问其他服务:
将配置委托传递给 OptionsBuilder<TOptions > 上的 Configure。 OptionsBuilder<TOptions> 提供 Configure 的重载,该重载允许使用最多五个服务来配置选项:
services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
创建实现 IConfigureOptions<TOptions> 或 IConfigureNamedOptions<TOptions> 的你自己的类型,并将该类型注册为服务。
建议将配置委托传递给 Configure,因为创建服务较复杂。 在使用 Configure时,创建你自己的类型等效于框架为你执行的操作。 调用 Configure 会注册临时泛型 IConfigureNamedOptions<TOptions>,它具有接受指定的泛型服务类型的构造函数。
选项后期配置
使用 IPostConfigureOptions<TOptions> 设置后期配置。 进行所有 IConfigureOptions<TOptions> 配置后运行后期配置:
services.PostConfigure<MyOptions>(myOptions => { myOptions.Option1 = "post_configured_option1_value"; });
PostConfigure 可用于对命名选项进行后期配置:
services.PostConfigure<MyOptions>("named_options_1", myOptions => { myOptions.Option1 = "post_configured_option1_value"; });
使用 PostConfigureAll 对所有配置实例进行后期配置:
services.PostConfigureAll<MyOptions>(myOptions => { myOptions.Option1 = "post_configured_option1_value"; });
在启动期间访问选项
IOptions<TOptions> 和 IOptionsMonitor<TOptions> 可用于 Startup.Configure
中,因为在 Configure
方法执行之前已生成服务。
public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor) { var option1 = optionsAccessor.CurrentValue.Option1; }
不使用 Startup.ConfigureServices
中的 IOptions<TOptions> 或 IOptionsMonitor<TOptions>。 由于服务注册的顺序,可能存在不一致的选项状态。