ASP.NET Core Options模式可以使用强类型的类来访问一组关联的配置
,其实Options模式的目的实现了软件工程中2个基本的原则
仅仅依赖于他们自己使用的配置
关注点分离
同时Options模式也提供了一种数据验证的机制
1. 绑定分层配置
我们在项目文件的appsettings.json文件中新增如下内容:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
接着在项目文件夹下创建 PositionOptions 类:
这个类需要注意以下几点:
该类必须是非抽象类
属性的类型必须与配置中的类型相符合,例如:配置中是string类型,你的属性也必须是string类型,而不能是int类型
属性的名称必须与配置中Key的名称保持一致
不能绑定字段
1.1 使用Bind方法
应用程序启动之后,如果修改配置文件这个方法是会自动读取最新的配置文件
1.2 使用Get<T>方法
应用程序启动之后,如果修改配置文件这个方法是会自动读取最新的配置文件
2. Options模式
2.1 IOptions <TOptions>接口
Singleton生命周期
不能在Singleton的生命周期的服务内使用
仅仅读取一次
应用程序启动之后,如果配置发生变更不会通知
我们可以使用Options模式替换上面方法,在启动类中添加如下方法,下面代码添加PositionOptions到容器:
builder.Services.Configure<PositionOptions>(
builder.Configuration.Getp(PositionOptions.Position));
有了上面的配置我们可以使用下面代码读取该配置:
using AspNetCore.OptionsPattern.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespaceAspNetCore.OptionsPattern.Controllers
{
public classIOptionsInterfaceController : Controller
{
private IOptions<PositionOptions> _positionOptions;
publicIOptionsInterfaceController(IOptions<PositionOptions> options)
{
_positionOptions = options;
}
public IActionResult Index()
{
return View(_positionOptions.Value);
}
}
}
2.2 IOptionsSnapshot<TOptions>接口
Scoped生命周期
不能在Singleton的生命周期的服务内使用
每个Scoped读取数据
配置的数据量如果特别大,性能非常差(https://github.com/dotnet/runtime/issues/53793)
using AspNetCore.OptionsPattern.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespaceAspNetCore.OptionsPattern.Controllers
{
public classIOptionsSnapshotInterfaceController : Controller
{
private IOptionsSnapshot<PositionOptions> _optionsSnapshot;
publicIOptionsSnapshotInterfaceController(IOptionsSnapshot<PositionOptions> optionsSnapshot)
{
_optionsSnapshot = optionsSnapshot;
}
public IActionResult Index()
{
return View(_optionsSnapshot.Value);
}
}
}
2.3 IOptionsMonitor<TOptions>接口
Singleton生命周期
能够在任何的生命周期的服务内使用
实时读取数据
通常在后台的服务中使用
变更通知(当配置发生变化的时候,会自动通知)
using AspNetCore.OptionsPattern.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace AspNetCore.OptionsPattern.Controllers
{
public class IOptionsMonitorInterfaceController : Controller
{
private IOptionsMonitor<PositionOptions> _positionOptions;
public IOptionsMonitorInterfaceController(IOptionsMonitor<PositionOptions> optionsMonitor)
{
_positionOptions = optionsMonitor;
}
public IActionResult Index()
{
return View(_positionOptions.CurrentValue);
}
}
}
3. Options数据验证
我们可以在绑定过程中对相应的值进行验证,将下面配置添加到appsettings.json文件中:
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
我们在定义一个类来绑定上面节点:
namespace AspNetCore.OptionsPattern.Models
{
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; }
}
}
在启动类中添加如下代码:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.Getp(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();
调用AddOptions方法来获取一个OptionsBuilder<TOptions>对象,绑定到MyConfigOptions类, ValidateDataAnnotations方法来启用DataAnnotations的验证
我们还可以做一些复杂的验证逻辑,如下代码所示:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.Getp(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.Validate(config =>
{
if (config.Key2 != 0)
{
return config.Key3 > config.Key2;
}
return true;
}, "Key3 must be > than Key2.");
我们也可以将验证逻辑放到单独的类中来实现,我们看一下Validate方法的源代码:
我们也可以自定义一个类来实现IValidateOptions<TOptions>实现我们的验证逻辑,接下来我们自定义一个验证逻辑类:
using Microsoft.Extensions.Options;
using System.Text.RegularExpressions;
namespace AspNetCore.OptionsPattern.Models
{
public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
public MyConfigOptions _config { get; private set; }
public MyConfigValidation(IConfiguration config)
{
_config = config.Getp(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;
}
}
}
修改启动类中的配置:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.Getp(MyConfigOptions.MyConfig));
builder.Services.AddSingleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>();
源代码地址:
https://github.com/bingbing-gui/Asp.Net-Core-Skill/tree/master/Fundamentals/AspNetCore.Options/AspNetCore.OptionsPattern