• 深入理解ASP.NET Core中的Options模式
  • 发布于 2个月前
  • 221 热度
    0 评论
  • 耀国
  • 0 粉丝 43 篇博客
  •   
ASP.NET Core Options模式可以使用强类型的类来访问一组关联的配置,其实Options模式的目的实现了软件工程中2个基本的原则:
1.仅仅依赖于他们自己使用的配置
2.关注点分离

同时Options模式也提供了一种数据验证的机制。


一. 绑定分层配置
我们在项目文件的appsettings.json文件中新增如下内容:
"Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }
接着在项目文件夹下创建PositionOptions类:

这个类需要注意以下几点:
1.该类必须是非抽象类
2.属性的类型必须与配置中的类型相符合,例如:配置中是string类型,你的属性也必须是string类型,而不能是int类型
3.属性的名称必须与配置中Key的名称保持一致
4.不能绑定字段
1.1 使用Bind方法
应用程序启动之后,如果修改配置文件这个方法是会自动读取最新的配置文件

1.2 使用Get<T>方法
应用程序启动之后,如果修改配置文件这个方法是会自动读取最新的配置文件

二. Options模式
2.1 IOptions<TOptions>接口
.Singleton生命周期
.不能在Singleton的生命周期的服务内使用
.仅仅读取一次
.应用程序启动之后,如果配置发生变更不会通知
我们可以使用Options模式替换上面方法,在启动类中添加如下方法,下面代码添加PositionOptions到容器:
builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
有了上面的配置我们可以使用下面代码读取该配置:
using AspNetCore.OptionsPattern.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace AspNetCore.OptionsPattern.Controllers
{
    public class IOptionsInterfaceController : Controller
    {
        private IOptions<PositionOptions> _positionOptions;

        public IOptionsInterfaceController(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;
namespace AspNetCore.OptionsPattern.Controllers
{
    public class IOptionsSnapshotInterfaceController : Controller
    {
        private IOptionsSnapshot<PositionOptions> _optionsSnapshot;
        public IOptionsSnapshotInterfaceController(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);
        }
    }
}
三. Options数据验证
我们可以在绑定过程中对相应的值进行验证,将下面配置添加到appsettings.json文件中:
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
我们在定义一个类来绑定上面节点:
namespace AspNetCore.OptionsPattern.Models
{
    public class MyConfigOptions
    {
        public const string MyConfig = "MyConfig";
        // 堆代码 duidaima.com
        [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.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations();
调用AddOptions方法来获取一个OptionsBuilder<TOptions>对象,绑定到MyConfigOptions类,ValidateDataAnnotations方法来启用DataAnnotations的验证
我们还可以做一些复杂的验证逻辑,如下代码所示:
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."); 
我们也可以将验证逻辑放到单独的类中来实现。我们也可以自定义一个类来实现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.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;
        }
    }
}
修改启动类中的配置:
builder.Services.AddOptions<MyConfigOptions>()
           .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.AddSingleton<IValidateOptions
                              <MyConfigOptions>, MyConfigValidation>();

用户评论