Note
此版本不是本文的最新版本。 有关当前版本,请参阅本文的 .NET 9 版本。
Warning
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅本文的 .NET 9 版本。
Important
此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。
有关当前版本,请参阅本文的 .NET 9 版本。
作者:Rick Anderson。
选项模式使用类来提供对相关设置组的强类型访问。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
-
Encapsulation:
- 依赖于配置设置的类仅依赖于其使用的配置设置。
-
分离关注点:
- 应用的不同部件的设置不彼此依赖或相互耦合。
选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。
本文介绍 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实例的选项通知。 - 注册为单一实例且可以注入到任何服务生存期。
- Supports:
- 更改通知
- 命名选项
- 可重载配置
- 选择性选项失效 (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 配置文件所做的更改。
使用 ConfigurationKeyName
默认情况下,options 类的属性名称用作配置源中的键名称。 如果属性名称为 Title,则配置中的键名称也为 Title 。
当名称不同时,您可以使用该ConfigurationKeyName属性在配置源中指定键名称。 使用此技术,您可以将配置中的属性映射到代码中具有不同名称的属性。
当配置源中的属性名称不是有效的 C# 标识符或您想在代码中使用其他名称时,这非常有用。
例如,考虑以下 options 类:
public class PositionOptionsWithConfigurationKeyName
{
public const string Position = "Position";
[ConfigurationKeyName("position-title")]
public string Title { get; set; } = string.Empty;
[ConfigurationKeyName("position-name")]
public string Name { get; set; } = string.Empty;
}
和 TitleName class 属性绑定到以下position-title文件中的 position-name andappsettings.json:
{
"Position": {
"position-title": "Editor",
"position-name": "Joe Smith"
}
}
命名选项支持使用 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 服务配置选项
在配置选项时,可以通过以下两种方式通过依赖关系注入访问服务:
将配置委托传递给 Configure 上的 OptionsBuilder<TOptions>。
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 以获取绑定到 OptionsBuilder<TOptions> 类的
MyConfigOptions。 - 调用 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 方法。 - 在 ValidateDataAnnotations 中调用
Program.cs。
ValidateOnStart
首次创建实例时 TOption ,选项验证将运行。 例如,在请求管道中首次访问 IOptionsSnapshot<TOptions>.Value 时或在设置存在时 IOptionsMonitor<TOptions>.Get(string) 调用时。 重新加载设置后,验证会再次运行。 ASP.NET Core 运行时在创建选项实例后用于 OptionsCache<TOptions> 缓存选项实例。
若要立即运行选项验证,请在应用启动时调用 ValidateOnStart<TOptions>(OptionsBuilder<TOptions>) 中的 Program.cs:
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 中的选项
若要访问 IOptions<TOptions> 中的 IOptionsMonitor<TOptions> 或 Program.cs,请在 GetRequiredService 上调用 WebApplication.Services:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
其他资源
作者:Kirk Larkin 和 Rick Anderson。
选项模式使用类来提供对相关设置组的强类型访问。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
-
Encapsulation:
- 依赖于配置设置的类仅依赖于其使用的配置设置。
-
分离关注点:
- 应用的不同部件的设置不彼此依赖或相互耦合。
选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。
本文介绍 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实例的选项通知。 - 注册为单一实例且可以注入到任何服务生存期。
- Supports:
- 更改通知
- 命名选项
- 可重载配置
- 选择性选项失效 (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 服务配置选项
在配置选项时,可以通过以下两种方式通过依赖关系注入访问服务:
将配置委托传递给 Configure 上的 OptionsBuilder<TOptions>。
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 以获取绑定到 OptionsBuilder<TOptions> 类的
MyConfigOptions。 - 调用 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 方法。 - 在 ValidateDataAnnotations 中调用
Program.cs。
ValidateOnStart
选项验证将在第一次创建 IOptions<TOptions>、IOptionsSnapshot<TOptions> 或 IOptionsMonitor<TOptions> 实现时运行。 若要立即运行选项验证,请在应用启动时调用 ValidateOnStart 中的 Program.cs:
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 中的选项
若要访问 IOptions<TOptions> 中的 IOptionsMonitor<TOptions> 或 Program.cs,请在 GetRequiredService 上调用 WebApplication.Services:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
其他资源
作者:Kirk Larkin 和 Rick Anderson。
选项模式使用类来提供对相关设置组的强类型访问。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
-
Encapsulation:
- 依赖于配置设置的类仅依赖于其使用的配置设置。
-
分离关注点:
- 应用的不同部件的设置不彼此依赖或相互耦合。
选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。
本主题介绍 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实例的选项通知。 - 注册为单一实例且可以注入到任何服务生存期。
- Supports:
- 更改通知
- 命名选项
- 可重载配置
- 选择性选项失效 (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 服务配置选项
在配置选项时,可以通过以下两种方式通过依赖关系注入访问服务:
将配置委托传递给 Configure 上的 OptionsBuilder<TOptions>。
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 以获取绑定到 OptionsBuilder<TOptions> 类的
MyConfigOptions。 - 调用 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;
}
不使用 IOptions<TOptions> 中的 IOptionsMonitor<TOptions> 或 Startup.ConfigureServices。 由于服务注册的顺序,可能存在不一致的选项状态。
ConfigurationExtensions NuGet 包
Microsoft.Extensions.Options.ConfigurationExtensions 包已在 ASP.NET Core 应用中隐式引用。