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