ASP.NET Core 中的选项模式
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
作者:Rick Anderson。
选项模式使用类来提供对相关设置组的强类型访问。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。
本文介绍 ASP.NET Core 中的选项模式。 若要了解如何在控制台应用中使用选项模式,请参阅 .NET 中的选项模式。
绑定分层配置
读取相关配置值的首选方法是使用选项模式。 例如,若要读取以下配置值,请执行以下操作:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
创建以下 PositionOptions
类:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
选项类:
- 必须是非抽象的。
- 绑定在配置中具有相应项的类型的公共读写属性。
- 将其读写属性绑定到配置中的匹配条目。
- 请勿绑定字段。 在上面的代码中,
Position
未绑定。 由于使用了Position
字段,因此在将类绑定到配置提供程序时,不需要在应用中对字符串"Position"
进行硬编码。
下面的代码:
- 调用 ConfigurationBinder.Bind 将
PositionOptions
类绑定到Position
部分。 - 显示
Position
配置数据。
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}");
}
}
在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。
ConfigurationBinder.Get<T>
绑定并返回指定的类型。 使用 ConfigurationBinder.Get<T>
可能比使用 ConfigurationBinder.Bind
更方便。 下面的代码演示如何将 ConfigurationBinder.Get<T>
与 PositionOptions
类配合使用:
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}");
}
}
在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。
Bind 还允许抽象类的缩进。 请看以下使用抽象类 SomethingWithAName
的代码:
namespace ConfigSample.Options;
public abstract class SomethingWithAName
{
public abstract string? Name { get; set; }
}
public class NameTitleOptions(int age) : SomethingWithAName
{
public const string NameTitle = "NameTitle";
public override string? Name { get; set; }
public string Title { get; set; } = string.Empty;
public int Age { get; set; } = age;
}
以下代码显示 NameTitleOptions
配置值:
public class Test33Model : PageModel
{
private readonly IConfiguration Configuration;
public Test33Model(IConfiguration configuration)
{
Configuration = configuration;
}
public ContentResult OnGet()
{
var nameTitleOptions = new NameTitleOptions(22);
Configuration.GetSection(NameTitleOptions.NameTitle).Bind(nameTitleOptions);
return Content($"Title: {nameTitleOptions.Title} \n" +
$"Name: {nameTitleOptions.Name} \n" +
$"Age: {nameTitleOptions.Age}"
);
}
}
对 Bind
的调用比对 Get<>
的调用严格:
Bind
允许抽象的缩进。Get<>
必须创建实例本身。
选项模式
使用选项模式时的另一种方法是绑定 Position
部分,并将其添加到依赖关系注入服务容器中。 在以下代码中,PositionOptions
已通过 Configure 被添加到了服务容器并已绑定到了配置:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
通过使用前面的代码,以下代码将读取位置选项:
public class Test2Model : PageModel
{
private readonly PositionOptions _options;
public Test2Model(IOptions<PositionOptions> options)
{
_options = options.Value;
}
public ContentResult OnGet()
{
return Content($"Title: {_options.Title} \n" +
$"Name: {_options.Name}");
}
}
在上面的代码中,不会读取在应用启动后对 JSON 配置文件所做的更改。 若要读取在应用启动后的更改,请使用 IOptionsSnapshot。
选项接口
- 在每次请求时应重新计算选项的方案中有用。 有关详细信息,请参阅使用 IOptionsSnapshot 读取已更新的数据。
- 注册为范围内,因此无法注入到单一实例服务。
- 支持命名选项
- 用于检索选项并管理
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 读取已更新的数据
使用 IOptionsSnapshot<TOptions>:
- 针对请求生存期访问和缓存选项时,每个请求只能计算一次选项。
- 可能会产生显著的性能损失,因为它是范围内服务,并按请求重新计算。 有关详细信息,请参阅此 GitHub 问题和改进配置绑定的性能。
- 当使用支持读取已更新的配置值的配置提供程序时,将在应用启动后读取对配置所做的更改。
IOptionsMonitor
和 IOptionsSnapshot
之间的区别在于:
IOptionsMonitor
是一种单一示例服务,可随时检索当前选项值,这在单一实例依赖项中尤其有用。IOptionsSnapshot
是一种范围服务,并在构造IOptionsSnapshot<T>
对象时提供选项的快照。 选项快照旨在用于暂时性和有作用域的依赖项。
以下代码使用 IOptionsSnapshot<TOptions>。
public class TestSnapModel : PageModel
{
private readonly MyOptions _snapshotOptions;
public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
{
_snapshotOptions = snapshotOptionsAccessor.Value;
}
public ContentResult OnGet()
{
return Content($"Option1: {_snapshotOptions.Option1} \n" +
$"Option2: {_snapshotOptions.Option2}");
}
}
以下代码注册 MyOptions
绑定的配置实例:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
在上面的代码中,已读取在应用启动后对 JSON 配置文件所做的更改。
IOptionsMonitor
以下代码注册 MyOptions
绑定的配置实例。
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
下面的示例使用 IOptionsMonitor<TOptions>:
public class TestMonitorModel : PageModel
{
private readonly IOptionsMonitor<MyOptions> _optionsDelegate;
public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
{
_optionsDelegate = optionsDelegate;
}
public ContentResult OnGet()
{
return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
$"Option2: {_optionsDelegate.CurrentValue.Option2}");
}
}
在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。
命名选项支持使用 IConfigureNamedOptions
命名选项:
- 当多个配置节绑定到同一属性时有用。
- 区分大小写。
请考虑以下 appsettings.json
文件:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
下面的类用于每个节,而不是创建两个类来绑定 TopItem:Month
和 TopItem:Year
:
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; } = string.Empty;
public string Model { get; set; } = string.Empty;
}
下面的代码将配置命名选项:
using SampleApp.Models;
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"));
var app = builder.Build();
下面的代码将显示命名选项:
public class TestNOModel : PageModel
{
private readonly TopItemSettings _monthTopItem;
private readonly TopItemSettings _yearTopItem;
public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
{
_monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
_yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
}
public ContentResult OnGet()
{
return Content($"Month:Name {_monthTopItem.Name} \n" +
$"Month:Model {_monthTopItem.Model} \n\n" +
$"Year:Name {_yearTopItem.Name} \n" +
$"Year:Model {_yearTopItem.Model} \n" );
}
}
所有选项都是命名实例。 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
在选项验证部分中使用。
有关添加自定义存储库的信息,请参阅使用 AddOptions 配置自定义存储库。
使用 DI 服务配置选项
在配置选项时,可以通过以下两种方式通过依赖关系注入访问服务:
将配置委托传递给 OptionsBuilder<TOptions> 上的 Configure。
OptionsBuilder<TOptions>
提供 Configure 的重载,该重载允许使用最多五个服务来配置选项: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
文件:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
使用下面的类绑定到 "MyConfig"
配置节,并应用若干 DataAnnotations
规则:
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
启用验证。
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();
ValidateDataAnnotations
扩展方法在 Microsoft.Extensions.Options.DataAnnotations NuGet 包中定义。 对于使用 Microsoft.NET.Sdk.Web
SDK 的 Web 应用,通过共享框架隐式引用此包。
下面的代码显示配置值或验证错误:
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);
}
下面的代码使用委托应用更复杂的验证规则:
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."); // Failure message.
var app = builder.Build();
IValidateOptions<TOptions>
和 IValidatableObject
下面的类实现了 IValidateOptions<TOptions>:
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;
}
}
IValidateOptions
允许将验证代码移出 Program.cs
并将其移入类中。
使用前面的代码,使用以下代码在 Program.cs
中启用验证:
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
接口及其 Validate 方法。 - 在
Program.cs
中调用 ValidateDataAnnotations。
ValidateOnStart
首次创建实例时 TOption
,选项验证将运行。 例如,在请求管道中首次访问 IOptionsSnapshot<TOptions>.Value
时或在设置存在时 IOptionsMonitor<TOptions>.Get(string)
调用时。 重新加载设置后,验证会再次运行。 ASP.NET Core 运行时在创建选项实例后用于 OptionsCache<TOptions> 缓存选项实例。
若要立即运行选项验证,请在应用启动时调用 Program.cs
中的 ValidateOnStart<TOptions>(OptionsBuilder<TOptions>):
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
选项后期配置
使用 IPostConfigureOptions<TOptions> 设置后期配置。 进行所有 IConfigureOptions<TOptions> 配置后运行后期配置:
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 可用于对命名选项进行后期配置:
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 对所有配置实例进行后期配置:
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:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
其他资源
作者:Kirk Larkin 和 Rick Anderson。
选项模式使用类来提供对相关设置组的强类型访问。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。
本文介绍 ASP.NET Core 中的选项模式。 若要了解如何在控制台应用中使用选项模式,请参阅 .NET 中的选项模式。
绑定分层配置
读取相关配置值的首选方法是使用选项模式。 例如,若要读取以下配置值,请执行以下操作:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
创建以下 PositionOptions
类:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
选项类:
- 必须是包含公共无参数构造函数的非抽象类。
- 类型的所有公共读写属性都已绑定。
- 字段不是绑定的。 在上面的代码中,
Position
未绑定。 由于使用了Position
字段,因此在将类绑定到配置提供程序时,不需要在应用中对字符串"Position"
进行硬编码。
下面的代码:
- 调用 ConfigurationBinder.Bind 将
PositionOptions
类绑定到Position
部分。 - 显示
Position
配置数据。
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}");
}
}
在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。
ConfigurationBinder.Get<T>
绑定并返回指定的类型。 使用 ConfigurationBinder.Get<T>
可能比使用 ConfigurationBinder.Bind
更方便。 下面的代码演示如何将 ConfigurationBinder.Get<T>
与 PositionOptions
类配合使用:
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}");
}
}
在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。
使用选项模式时的另一种方法是绑定 Position
部分,并将其添加到依赖关系注入服务容器中。 在以下代码中,PositionOptions
已通过 Configure 被添加到了服务容器并已绑定到了配置:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
通过使用前面的代码,以下代码将读取位置选项:
public class Test2Model : PageModel
{
private readonly PositionOptions _options;
public Test2Model(IOptions<PositionOptions> options)
{
_options = options.Value;
}
public ContentResult OnGet()
{
return Content($"Title: {_options.Title} \n" +
$"Name: {_options.Name}");
}
}
在上面的代码中,不会读取在应用启动后对 JSON 配置文件所做的更改。 若要读取在应用启动后的更改,请使用 IOptionsSnapshot。
选项接口
- 在每次请求时应重新计算选项的方案中有用。 有关详细信息,请参阅使用 IOptionsSnapshot 读取已更新的数据。
- 注册为范围内,因此无法注入到单一实例服务。
- 支持命名选项
- 用于检索选项并管理
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 读取已更新的数据
使用 IOptionsSnapshot<TOptions>:
- 针对请求生存期访问和缓存选项时,每个请求只能计算一次选项。
- 可能会产生显著的性能损失,因为它是范围内服务,并按请求重新计算。 有关详细信息,请参阅此 GitHub 问题和改进配置绑定的性能。
- 当使用支持读取已更新的配置值的配置提供程序时,将在应用启动后读取对配置所做的更改。
IOptionsMonitor
和 IOptionsSnapshot
之间的区别在于:
IOptionsMonitor
是一种单一示例服务,可随时检索当前选项值,这在单一实例依赖项中尤其有用。IOptionsSnapshot
是一种范围服务,并在构造IOptionsSnapshot<T>
对象时提供选项的快照。 选项快照旨在用于暂时性和有作用域的依赖项。
以下代码使用 IOptionsSnapshot<TOptions>。
public class TestSnapModel : PageModel
{
private readonly MyOptions _snapshotOptions;
public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
{
_snapshotOptions = snapshotOptionsAccessor.Value;
}
public ContentResult OnGet()
{
return Content($"Option1: {_snapshotOptions.Option1} \n" +
$"Option2: {_snapshotOptions.Option2}");
}
}
以下代码注册 MyOptions
绑定的配置实例:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
在上面的代码中,已读取在应用启动后对 JSON 配置文件所做的更改。
IOptionsMonitor
以下代码注册 MyOptions
绑定的配置实例。
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
下面的示例使用 IOptionsMonitor<TOptions>:
public class TestMonitorModel : PageModel
{
private readonly IOptionsMonitor<MyOptions> _optionsDelegate;
public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
{
_optionsDelegate = optionsDelegate;
}
public ContentResult OnGet()
{
return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
$"Option2: {_optionsDelegate.CurrentValue.Option2}");
}
}
在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。
命名选项支持使用 IConfigureNamedOptions
命名选项:
- 当多个配置节绑定到同一属性时有用。
- 区分大小写。
请考虑以下 appsettings.json
文件:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
下面的类用于每个节,而不是创建两个类来绑定 TopItem:Month
和 TopItem:Year
:
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; } = string.Empty;
public string Model { get; set; } = string.Empty;
}
下面的代码将配置命名选项:
using SampleApp.Models;
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"));
var app = builder.Build();
下面的代码将显示命名选项:
public class TestNOModel : PageModel
{
private readonly TopItemSettings _monthTopItem;
private readonly TopItemSettings _yearTopItem;
public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
{
_monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
_yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
}
public ContentResult OnGet()
{
return Content($"Month:Name {_monthTopItem.Name} \n" +
$"Month:Model {_monthTopItem.Model} \n\n" +
$"Year:Name {_yearTopItem.Name} \n" +
$"Year:Model {_yearTopItem.Model} \n" );
}
}
所有选项都是命名实例。 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
在选项验证部分中使用。
有关添加自定义存储库的信息,请参阅使用 AddOptions 配置自定义存储库。
使用 DI 服务配置选项
在配置选项时,可以通过以下两种方式通过依赖关系注入访问服务:
将配置委托传递给 OptionsBuilder<TOptions> 上的 Configure。
OptionsBuilder<TOptions>
提供 Configure 的重载,该重载允许使用最多五个服务来配置选项: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
文件:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
使用下面的类绑定到 "MyConfig"
配置节,并应用若干 DataAnnotations
规则:
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
启用验证。
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();
ValidateDataAnnotations
扩展方法在 Microsoft.Extensions.Options.DataAnnotations NuGet 包中定义。 对于使用 Microsoft.NET.Sdk.Web
SDK 的 Web 应用,通过共享框架隐式引用此包。
下面的代码显示配置值或验证错误:
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);
}
下面的代码使用委托应用更复杂的验证规则:
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."); // Failure message.
var app = builder.Build();
IValidateOptions<TOptions>
和 IValidatableObject
下面的类实现了 IValidateOptions<TOptions>:
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;
}
}
IValidateOptions
允许将验证代码移出 Program.cs
并将其移入类中。
使用前面的代码,使用以下代码在 Program.cs
中启用验证:
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
接口及其 Validate 方法。 - 在
Program.cs
中调用 ValidateDataAnnotations。
ValidateOnStart
选项验证将在第一次创建 IOptions<TOptions>、IOptionsSnapshot<TOptions> 或 IOptionsMonitor<TOptions> 实现时运行。 若要立即运行选项验证,请在应用启动时调用 Program.cs
中的 ValidateOnStart:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
选项后期配置
使用 IPostConfigureOptions<TOptions> 设置后期配置。 进行所有 IConfigureOptions<TOptions> 配置后运行后期配置:
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 可用于对命名选项进行后期配置:
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 对所有配置实例进行后期配置:
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:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
其他资源
作者:Kirk Larkin 和 Rick Anderson。
选项模式使用类来提供对相关设置组的强类型访问。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。
本主题介绍 ASP.NET Core 中的选项模式。 若要了解如何在控制台应用中使用选项模式,请参阅 .NET 中的选项模式。
绑定分层配置
读取相关配置值的首选方法是使用选项模式。 例如,若要读取以下配置值,请执行以下操作:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
创建以下 PositionOptions
类:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; }
public string Name { get; set; }
}
选项类:
- 必须是包含公共无参数构造函数的非抽象类。
- 类型的所有公共读写属性都已绑定。
- 字段不是绑定的。 在上面的代码中,
Position
未绑定。 由于使用了Position
属性,因此在将类绑定到配置提供程序时,不需要在应用中对字符串"Position"
进行硬编码。
下面的代码:
- 调用 ConfigurationBinder.Bind 将
PositionOptions
类绑定到Position
部分。 - 显示
Position
配置数据。
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}");
}
}
在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。
ConfigurationBinder.Get<T>
绑定并返回指定的类型。 使用 ConfigurationBinder.Get<T>
可能比使用 ConfigurationBinder.Bind
更方便。 下面的代码演示如何将 ConfigurationBinder.Get<T>
与 PositionOptions
类配合使用:
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}");
}
}
在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。
使用选项模式时的另一种方法是绑定 Position
部分,并将其添加到依赖关系注入服务容器中。 在以下代码中,PositionOptions
已通过 Configure 被添加到了服务容器并已绑定到了配置:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PositionOptions>(Configuration.GetSection(
PositionOptions.Position));
services.AddRazorPages();
}
通过使用前面的代码,以下代码将读取位置选项:
public class Test2Model : PageModel
{
private readonly PositionOptions _options;
public Test2Model(IOptions<PositionOptions> options)
{
_options = options.Value;
}
public ContentResult OnGet()
{
return Content($"Title: {_options.Title} \n" +
$"Name: {_options.Name}");
}
}
在上面的代码中,不会读取在应用启动后对 JSON 配置文件所做的更改。 若要读取在应用启动后的更改,请使用 IOptionsSnapshot。
选项接口
- 在每次请求时应重新计算选项的方案中有用。 有关详细信息,请参阅使用 IOptionsSnapshot 读取已更新的数据。
- 注册为范围内,因此无法注入到单一实例服务。
- 支持命名选项
- 用于检索选项并管理
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 读取已更新的数据
通过使用 IOptionsSnapshot<TOptions>,针对请求生存期访问和缓存选项时,每个请求都会计算一次选项。 当使用支持读取已更新的配置值的配置提供程序时,将在应用启动后读取对配置所做的更改。
IOptionsMonitor
和 IOptionsSnapshot
之间的区别在于:
IOptionsMonitor
是一种单一示例服务,可随时检索当前选项值,这在单一实例依赖项中尤其有用。IOptionsSnapshot
是一种范围服务,并在构造IOptionsSnapshot<T>
对象时提供选项的快照。 选项快照旨在用于暂时性和有作用域的依赖项。
以下代码使用 IOptionsSnapshot<TOptions>。
public class TestSnapModel : PageModel
{
private readonly MyOptions _snapshotOptions;
public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
{
_snapshotOptions = snapshotOptionsAccessor.Value;
}
public ContentResult OnGet()
{
return Content($"Option1: {_snapshotOptions.Option1} \n" +
$"Option2: {_snapshotOptions.Option2}");
}
}
以下代码注册 MyOptions
绑定的配置实例:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
在上面的代码中,已读取在应用启动后对 JSON 配置文件所做的更改。
IOptionsMonitor
以下代码注册 MyOptions
绑定的配置实例。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
下面的示例使用 IOptionsMonitor<TOptions>:
public class TestMonitorModel : PageModel
{
private readonly IOptionsMonitor<MyOptions> _optionsDelegate;
public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
{
_optionsDelegate = optionsDelegate;
}
public ContentResult OnGet()
{
return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
$"Option2: {_optionsDelegate.CurrentValue.Option2}");
}
}
在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。
命名选项支持使用 IConfigureNamedOptions
命名选项:
- 当多个配置节绑定到同一属性时有用。
- 区分大小写。
请考虑以下 appsettings.json
文件:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
下面的类用于每个节,而不是创建两个类来绑定 TopItem:Month
和 TopItem:Year
:
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; }
public string Model { get; set; }
}
下面的代码将配置命名选项:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<TopItemSettings>(TopItemSettings.Month,
Configuration.GetSection("TopItem:Month"));
services.Configure<TopItemSettings>(TopItemSettings.Year,
Configuration.GetSection("TopItem:Year"));
services.AddRazorPages();
}
下面的代码将显示命名选项:
public class TestNOModel : PageModel
{
private readonly TopItemSettings _monthTopItem;
private readonly TopItemSettings _yearTopItem;
public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
{
_monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
_yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
}
public ContentResult OnGet()
{
return Content($"Month:Name {_monthTopItem.Name} \n" +
$"Month:Model {_monthTopItem.Model} \n\n" +
$"Year:Name {_yearTopItem.Name} \n" +
$"Year:Model {_yearTopItem.Model} \n" );
}
}
所有选项都是命名实例。 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
在选项验证部分中使用。
有关添加自定义存储库的信息,请参阅使用 AddOptions 配置自定义存储库。
使用 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>,它具有可接受指定的通用服务类型的构造函数。
选项验证
通过选项验证,可以验证选项值。
请考虑以下 appsettings.json
文件:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
下面的类绑定到 "MyConfig"
配置节,并应用若干 DataAnnotations
规则:
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
启用验证。
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions<MyConfigOptions>()
.Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();
services.AddControllersWithViews();
}
ValidateDataAnnotations
扩展方法在 Microsoft.Extensions.Options.DataAnnotations NuGet 包中定义。 对于使用 Microsoft.NET.Sdk.Web
SDK 的 Web 应用,通过共享框架隐式引用此包。
下面的代码显示配置值或验证错误:
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);
}
下面的代码使用委托应用更复杂的验证规则:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions<MyConfigOptions>()
.Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.Validate(config =>
{
if (config.Key2 != 0)
{
return config.Key3 > config.Key2;
}
return true;
}, "Key3 must be > than Key2."); // Failure message.
services.AddControllersWithViews();
}
用于复杂验证的 IValidateOptions
下面的类实现了 IValidateOptions<TOptions>:
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;
}
}
IValidateOptions
允许将验证代码移出 StartUp
并将其移入类中。
使用前面的代码,使用以下代码在 Startup.ConfigureServices
中启用验证:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyConfigOptions>(Configuration.GetSection(
MyConfigOptions.MyConfig));
services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>());
services.AddControllersWithViews();
}
选项后期配置
使用 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>。 由于服务注册的顺序,可能存在不一致的选项状态。
ConfigurationExtensions NuGet 包
Microsoft.Extensions.Options.ConfigurationExtensions 包已在 ASP.NET Core 应用中隐式引用。