Appearance
.NET Options 模式
1. 绑定 options
一个 options 类应符合:
- 非抽象类
- 包含
public无参构造函数 - 所有
public的读写属性都绑定 - 字段不绑定
若要读取以下配置值:
JSON
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}1
2
3
4
2
3
4
首先创建一个 options 类:
csharp
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}1
2
3
4
5
6
7
2
3
4
5
6
7
1.1. ConfigurationBinder.Bind 或 ConfigurationBinder.Get<T>
- 使用 ConfigurationBinder.Bind 方法将
options类绑定到对应的配置部分
csharp
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}");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 使用
ConfigurationBinder.Get<T>绑定options:
csharp
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}");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
以上两种绑定方法在默认情况下,应用程序启动后,对 JSON 文件的修改都会同步到 options 对象。
1.2. Configure<TOptions> 或 AddOptions<TOptions>
- 使用
Configure方法将options添加到容器中:
csharp
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 使用
IOptions<TOptions>从容器中获取options:
csharp
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}");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在本示例中,应用程序启动后,对 JSON 文件的修改不会再同步到 options 对象。若要读取在应用启动后的更改,请参考:IOptionsSnapshot。
AddOptions<TOptions>:
OptionsBuilder<TOptions>简化了AddOptions<TOptions>(string optionsName)方法申明,使其仅有一个optionsName参数,其余的配置在OptionsBuidler中完成。- 有关添加自定义存储库的信息,请参阅使用 AddOptions 配置自定义存储库。
2. Options 接口
- 不支持命名的
options - 被注册为单一实例并且可以注入到任何生命周期的服务
- 不支持在应用启动后读取配置数据
2.1. IOptionsSnapshot<TOptions>
- 支持命名的 options
- 被注册为
scoped服务,不允许被注入到单实例服务 - 在每个请求生命周期内
options有且只会计算一次 - 适用于每次请求都需要重新获取配置的场景,更多相关信息请参考:使用
IOptionsSnapshot读取已更新的配置 - 可能会导致性能问题,因为它被注册为 Scoped,所以在每次请求中都会被重新计算一次,可以参考:GitHub issue、优化 options 绑定性能
- 当使用支持读取已更新的配置值的配置提供程序时,将在应用启动后读取对配置所做的更改
csharp
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
csharp
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}");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2.2. IOptionsMonitor<TOptions>
- 支持更改通知
- 支持可重载配置
- 支持命名的
options - 支持手动让
options失效(IOptionsMonitorCache<TOptions>) - 被注册为单一实例并且可以注入到任何生命周期的服务
- 用于检索
options和管理options通知 IOptionsMonitor<TOptions>使用IOptionsMonitorCache<TOptions>缓存 options 实例。
csharp
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
csharp
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}");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2.3. IOptionsSnapshot 与 IOptionsMonitor 之间的区别
IOptionsSnapshot是 Scoped 服务,是IOptionsSnapshot创建时的options的快照IOptionsMonitor是单一实例服务,可以随时检索当前options值
2.4. IConfigureNamedOptions<TOptions>
IConfigureNamedOptions大小写敏感IConfigureNamedOptions<TOptions>也实现了IConfigureOptions<TOptions>接口IConfigureOptions<TOptions>实例被视为面向Options.DefaultName实例,即string.Emptynull命名选项用于面向所有命名实例,而不是某一特定命名的实例,ConfigureAll和PostConfigureAll遵循此约定
JSON
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
csharp
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;
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
csharp
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();1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
csharp
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" );
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2.5. IPostConfigureOptions<TOptions>
在所有 IConfigureOptions<TOptions> 配置完成之后,可以使用 Post-configuration(后期配置)对 options 进行修改。
2.6. IOptionsFactory<TOptions>
IOptionsFactory<TOptions>负责用来创建options实例- 默认在所有
IConfigureOptions<TOptions>和IPostConfigureOptions<TOptions>完成所有设置之后再创建 - 它能够区分
IConfigureNamedOptions<TOptions>和IConfigureOptions<TOptions>且仅调用适当的接口
3. 使用 DI 服务配置选项
在配置 options 时,可以通过以下两种方式注入依赖服务:
使用
OptionsBuilder<TOptions>的 Configure 方法(推荐):csharpbuilder.Services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));1
2
3
4创建一个实现了
IConfigureOptions<TOptions>或IConfigureNamedOptions<TOptions>的类,并将其注册为服务
4. Options 验证
JSON
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
csharp
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; }
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
csharp
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
2
3
4
5
6
7
8
9
10
11
csharp
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
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
还支持更复杂的验证规则:
csharp
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();1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20