选项模式使用类来提供对相关设置组的强类型访问。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
封装:依赖于配置设置的类仅依赖于其使用的配置设置
分离关注点:应用的不同部件的设置不彼此依赖或相互耦合
绑定分层配置
读取相关配置值的首选方法是使用选项模式。 例如,若要读取以下配置值,请执行以下操作:
appsettings.json PositionOptions 通过Bind()方式 通过Get()方式(推荐) 1 2 3 4 5 6 { "Position" : { "Title" : "Editor" , "Name" : "Joe Smith" } }
选项类:
必须是包含公共无参数构造函数的非抽象类。
类型的所有公共读写属性都已绑定。
字段不是绑定的。 在下面的代码中,Position 未绑定。 由于使用了 Position 字段,因此在将类绑定到配置提供程序时,不需要在应用中对字符串 “Position” 进行硬编码。
1 2 3 4 5 6 7 public class PositionOptions { public const string Position = "Position" ; public string Title { get ; set ; } = String.Empty; public string Name { get ; set ; } = String.Empty; }
通过以下代码:
调用 ConfigurationBinder.Bind 将 PositionOptions 类绑定到 Position 部分。
显式 Position 配置数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Test22Model : PageModel { private readonly IConfiguration Configuration; public Test22Model (IConfiguration configuration ) { Configuration = configuration; } public ContentResult OnGet () { var positionOptions = new PositionOptions(); Configuration.GetSection(PositionOptions.Position).Bind(positionOptions); return Content($"Title: {positionOptions.Title} \n" + $"Name: {positionOptions.Name} " ); } }
ConfigurationBinder.Get<T>
绑定并返回指定的类型。 使用 ConfigurationBinder.Get<T>
可能比使用 ConfigurationBinder.Bind
更方便。 下面的代码演示如何将 ConfigurationBinder.Get<T>
与 PositionOptions
类配合使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Test21Model : PageModel { private readonly IConfiguration Configuration; public PositionOptions? positionOptions { get ; private set ; } public Test21Model (IConfiguration configuration ) { Configuration = configuration; } public ContentResult OnGet () { positionOptions = Configuration.GetSection(PositionOptions.Position) .Get<PositionOptions>(); return Content($"Title: {positionOptions.Title} \n" + $"Name: {positionOptions.Name} " ); } }
使用选项模式时的另一种方法是绑定 Position 部分,并将其添加到依赖关系注入服务容器中。 在以下代码中,PositionOptions 已通过 Configure 被添加到了服务容器并已绑定到了配置:
IOptionsMonitor
和 IOptionsSnapshot
之间的区别在于:
IOptionsMonitor
是一种单一示例服务,可随时检索当前选项值,这在单一实例依赖项中尤其有用。
IOptionsSnapshot
是一种范围服务,并在构造 IOptionsSnapshot 对象时提供选项的快照。 选项快照旨在用于暂时性和有作用域的依赖项。
特性
IOptionsMonitor
IOptionsSnapshot
作用范围
单例(Singleton)
请求范围(Scoped)
配置更新频率
实时更新,每次读取时获取最新值
每个请求获取一次,在整个请求期间保持相同的值
适用场景
适用于长期运行的单例服务,需要动态更新配置
适用于请求范围的场景,如控制器和短生命周期服务
是否可监控配置变化
可以使用 OnChange 监控配置变化
不支持自动监控配置变化
适用于
单例服务、动态更新场景
HTTP请求生命周期中的服务
选项接口
IOptions<TOptions>
不支持:
注册为单一实例且可以注入到任何服务生存期。
IOptionsSnapshot<TOptions>
在每次请求时应重新计算选项的方案中有用。
注册为范围内,因此无法注入到单一实例服务。
支持命名选项
IOptionsMonitor<TOptions>
用于检索选项并管理 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 方法。
命名选项:
OptionsBuilderAPI
OptionsBuilder<TOptions>
用于配置 TOptions 实例。 OptionsBuilder
简化了创建命名选项的过程,因为它只是初始 AddOptions<TOptions>(string optionsName)
调用的单个参数,而不会出现在所有后续调用中。 选项验证和接受服务依赖关系的 ConfigureOptions
重载仅可通过 OptionsBuilder
获得。
DI服务配置选项
在配置选项时,可以通过以下两种方式通过依赖关系注入访问服务:
将配置委托传递给 OptionsBuilder<TOptions>
上的 Configure
。 OptionsBuilder<TOptions>
提供 Configure
的重载,该重载允许使用最多五个服务来配置选项:
1 2 3 4 builder.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>
,它具有可接受指定的通用服务类型的构造函数。
选项验证
通过选项验证,可以验证选项值。
appsettings.json MyConfigOptions.cs Program.cs HomeController.cs 1 2 3 4 5 6 7 { "MyConfig" : { "Key1" : "My Key One" , "Key2" : 10 , "Key3" : 32 } }
1 2 3 4 5 6 7 8 9 10 11 public class MyConfigOptions { public const string MyConfig = "MyConfig" ; [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$" ) ] public string Key1 { get ; set ; } [Range(0, 1000, ErrorMessage = "Value for {0} must be between {1} and {2}." ) ] public int Key2 { get ; set ; } public int Key3 { get ; set ; } }
调用 AddOptions 以获取绑定到 MyConfigOptions 类的 OptionsBuilder<TOptions>
。
调用 ValidateDataAnnotations
以使用 DataAnnotations 启用验证。
1 2 3 4 5 6 7 8 9 10 11 using OptionsValidationSample.Configuration;var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllersWithViews(); builder.Services.AddOptions<MyConfigOptions>() .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig)) .ValidateDataAnnotations(); var app = builder.Build();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class HomeController : Controller { private readonly ILogger<HomeController> _logger; private readonly IOptions<MyConfigOptions> _config; public HomeController (IOptions<MyConfigOptions> config, ILogger<HomeController> logger ) { _config = config; _logger = logger; try { var configValue = _config.Value; } catch (OptionsValidationException ex) { foreach (var failure in ex.Failures) { _logger.LogError(failure); } } } public ContentResult Index () { string msg; try { msg = $"Key1: {_config.Value.Key1} \n" + $"Key2: {_config.Value.Key2} \n" + $"Key3: {_config.Value.Key3} " ; } catch (OptionsValidationException optValEx) { return Content(optValEx.Message); } return Content(msg); }
下面的代码使用委托应用更复杂的验证规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using OptionsValidationSample.Configuration;var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllersWithViews(); builder.Services.AddOptions<MyConfigOptions>() .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig)) .ValidateDataAnnotations() .Validate(config => { if (config.Key2 != 0 ) { return config.Key3 > config.Key2; } return true ; }, "Key3 must be > than Key2." ); var app = builder.Build();
IValidateOptions
下面的类实现了IvalidateOptions<TOptions>
:
MyConfigValidation.cs Program.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class MyConfigValidation : IValidateOptions <MyConfigOptions >{ public MyConfigOptions _config { get ; private set ; } public MyConfigValidation (IConfiguration config ) { _config = config.GetSection(MyConfigOptions.MyConfig) .Get<MyConfigOptions>(); } public ValidateOptionsResult Validate (string name, MyConfigOptions options ) { string ? vor = null ; var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$" ); var match = rx.Match(options.Key1!); if (string .IsNullOrEmpty(match.Value)) { vor = $"{options.Key1} doesn't match RegEx \n" ; } if ( options.Key2 < 0 || options.Key2 > 1000 ) { vor = $"{options.Key2} doesn't match Range 0 - 1000 \n" ; } if (_config.Key2 != default ) { if (_config.Key3 <= _config.Key2) { vor += "Key3 must be > than Key2." ; } } if (vor != null ) { return ValidateOptionsResult.Fail(vor); } return ValidateOptionsResult.Success; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 using Microsoft.Extensions.Options;using OptionsValidationSample.Configuration;var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllersWithViews(); builder.Services.Configure<MyConfigOptions>(builder.Configuration.GetSection( MyConfigOptions.MyConfig)); builder.Services.AddSingleton<IValidateOptions <MyConfigOptions>, MyConfigValidation>(); var app = builder.Build();
IValidatableObject
IValidatableObject
通常用于模型类的验证。它允许你定义自定义的验证逻辑,通常与数据注释(DataAnnotations)一起使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 using System.Collections.Generic;using System.ComponentModel.DataAnnotations;public class Person : IValidatableObject { [Required ] public string Name { get ; set ; } public int Age { get ; set ; } public IEnumerable<ValidationResult> Validate (ValidationContext validationContext ) { if (Age < 18 ) { yield return new ValidationResult ("Age must be at least 18." , new [] { nameof (Age ) }) ; } } }
ValidateOnStart
选项验证将在第一次创建 IOptions<TOptions>
、IOptionsSnapshot<TOptions>
或 IOptionsMonitor<TOptions>
实现时运行。 若要立即运行选项验证,请在应用启动时调用 Program.cs 中的 ValidateOnStart
:
1 2 3 4 builder.Services.AddOptions<MyConfigOptions>() .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig)) .ValidateDataAnnotations() .ValidateOnStart();
选项后期配置
使用 IPostConfigureOptions<TOptions>
设置后期配置。 进行所有 IConfigureOptions<TOptions>
配置后运行后期配置。用于在所有配置源都应用后,对选项对象进行进一步配置或修改的机制。它通常用于在其他配置(如 Configure 或外部配置文件绑定)完成后,执行某些额外的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 using OptionsValidationSample.Configuration;var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllersWithViews(); builder.Services.AddOptions<MyConfigOptions>() .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig)); builder.Services.PostConfigure<MyConfigOptions>(myOptions => { myOptions.Key1 = "post_configured_key1_value" ; });
PostConfigure
可用于对命名选项进行后期配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var builder = WebApplication.CreateBuilder(args);builder.Services.AddRazorPages(); builder.Services.Configure<TopItemSettings>(TopItemSettings.Month, builder.Configuration.GetSection("TopItem:Month" )); builder.Services.Configure<TopItemSettings>(TopItemSettings.Year, builder.Configuration.GetSection("TopItem:Year" )); builder.Services.PostConfigure<TopItemSettings>("Month" , myOptions => { myOptions.Name = "post_configured_name_value" ; myOptions.Model = "post_configured_model_value" ; }); var app = builder.Build();
使用 PostConfigureAll
对所有配置实例进行后期配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 using OptionsValidationSample.Configuration;var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllersWithViews(); builder.Services.AddOptions<MyConfigOptions>() .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig)); builder.Services.PostConfigureAll<MyConfigOptions>(myOptions => { myOptions.Key1 = "post_configured_key1_value" ; });
访问 Program.cs 中的选项
若要访问 Program.cs 中的 IOptions<TOptions>
或 IOptionsMonitor<TOptions>
,请在 WebApplication.Services
上调用 GetRequiredService
:
1 2 3 4 var app = builder.Build();var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>() .CurrentValue.Option1;