当前位置: 欣欣网 > 码农

ASP.NET Core中Options模式

2024-02-27码农

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