0%

NetCore中的选项框架

NetCore中的选项框架

服务组件依赖选项框架

  • 特性

    1. 支持单例模式读取配置
    2. 支持快照
    3. 支持配置变更通知
    4. 支持运行时动态修改选项值
  • 在设计我们的系统时需要遵循两个原则

    1. 接口分离原则(ISP),我们的类不应该依赖它不使用的配置
    2. 关注点分离(SOC),不同组件、服务、类之间的配置不应该相互依赖或耦合一般使用Option来作为服务的构造函数,有IOptions、IOptionsSnapshot、IOptionsMonitor
  • 定义服务的选项类OrderServiceOptions、服务的接口IOrderService和服务类OrderService,在这里OrderService服务依赖了OrderServiceOptions选项

    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
    public class OrderServiceOptions
    {
    //最大订单数
    public int MaxOrderCount { get; set; } = 100;
    }
    public class OrderService:IOrderService
    {
    private readonly IOptions<OrderServiceOptions> _options;
    /// <summary>
    /// 该服务依赖了OrderServiceOptions
    /// </summary>
    /// <param name="options"></param>
    public OrderService(IOptions<OrderServiceOptions> options)
    {
    _options = options;
    }
    /// <summary>
    /// 获取最大订单数
    /// </summary>
    /// <returns></returns>
    public int ShowMaxOrderCount()
    {
    return _options.Value.MaxOrderCount;
    }
    }
    public interface IOrderService
    {
    int ShowMaxOrderCount();
    }
    1
    2
    3
    4
    5
    6
    //StartUp.cs
    //使用Configure方法绑定配置中的数据和Options的值,服务只依赖了OrderServiceOptions选项框架
    //而并没有依赖我们的配置框架,也就是说服务只关注值是什么,它并不关心配置的值是从哪来的
    //即OrderService依赖了OrderServiceOptions,而OrderServiceOptions中的值和appsettings.json文件中的值绑定
    services.Configure<OrderServiceOptions>(Configuration.GetSection("OrderService"));
    services.AddSingleton<IOrderService, OrderService>();

选项数据的热更新

  • 关键类型:
    1. IOption<>是单例的,它不跟踪配置更新,读不到更新后的值。
    2. IOptionMonitor <>是单例的,它跟踪配置更新,总是读到最新的值。
    3. IOptionSnapshot<>是范围的,它在范围的生命周期中,不会更新,但它会读到范围生命周期创建前的变更。
  • 使用IOptionsSnapshot进行热更新,还是之前的代码,只是将IOption换成IOptionsSnapshot,注意使用IOptionsSnapshot时,注册服务需要换成作用域模式
1
2
3
4
5
6
7
8
9
10
11
12
private readonly IOptionsSnapshot<OrderServiceOptions> _options;
/// <summary>
/// 该服务依赖了OrderServiceOptions
/// </summary>
/// <param name="options"></param>
public OrderService(IOptionsSnapshot<OrderServiceOptions> options)
{
_options = options;
}
//StartUp
services.Configure<OrderServiceOptions>(Configuration.GetSection("OrderService"));
services.AddScoped<IOrderService, OrderService>();
  • 使用IOptionsMonitor进行热更新和IOptionsSnapshot相同,但IOptionsMonitor可以适用于单例模式,而且在取值时用的是CurrentValue属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private readonly IOptionsMonitor<OrderServiceOptions> _options;
/// <summary>
/// 该服务依赖了OrderServiceOptions
/// </summary>
/// <param name="options"></param>
public OrderService(IOptionsMonitor<OrderServiceOptions> options)
{
_options = options;
}
/// <summary>
/// 获取最大订单数
/// </summary>
/// <returns></returns>
public int ShowMaxOrderCount()
{
return _options.CurrentValue.MaxOrderCount;
}
  • 定义一个扩展方法来操作我们OrderService的注册,并在扩展方法中动态配置我们的选项对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public static class OrderServiceExtensions
    {
    public static IServiceCollection AddOrderServiceCollection(this IServiceCollection services,
    IConfiguration configuration)
    {
    //绑定配置中的数据和Options的值,服务只依赖了OrderServiceOptions选项框架
    //而并没有依赖我们的配置框架,也就是说服务只关注值是什么,它并不关心配置的值是从哪来的
    services.Configure<OrderServiceOptions>(configuration.GetSection("OrderService"));
    //动态配置我们的选项对象
    services.PostConfigure<OrderServiceOptions>(opt => { opt.MaxOrderCount += 100; });
    services.AddSingleton<IOrderService, OrderService>();
    return services;
    }
    }
    //StartUp
    services.AddOrderServiceCollection(Configuration);

    这样做的好处是,可以把StartUp类中的一些服务的注册给抽离出来,使得代码更有结构。

为选项数据添加验证

  • 直接注册验证函数
1
2
3
4
//绑定选项并为选项添加验证逻辑Configure(opt => { configuration.GetSection("OrderService").Bind(opt); })就相当于上面那一句
//Validate(opt => opt.MaxOrderCount <= 100);是用来添加验证,当MaxOrderCount小于等于100验证通过
services.AddOptions<OrderServiceOptions>().Configure(opt => { configuration.GetSection("OrderService").Bind(opt); })
.Validate(opt => opt.MaxOrderCount <= 100);
  • 实现DataAnnotations
1
2
3
4
5
6
7
//使用属性验证的方式
services.AddOptions<OrderServiceOptions>().Configure(opt => { configuration.GetSection("OrderService").Bind(opt); })
.ValidateDataAnnotations();

//在验证的属性上添加验证特性,表示MaxOrderCount的值为1到100
[Range(1, 100)]
public int MaxOrderCount { get; set; } = 100;
  • 使用IValidateOptions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//定义一个验证类,实现IValidateOptions接口,该接口必需实现一个Validate方法,用来添加验证逻辑
public class OrderServiceValidateOptions:IValidateOptions<OrderServiceOptions>
{
public ValidateOptionsResult Validate(string name, OrderServiceOptions options)
{
if (options.MaxOrderCount>100)
{
return ValidateOptionsResult.Fail("MaxOrderCount不能大于100");
}
return ValidateOptionsResult.Success;
}
}

//使用实现接口验证的方式
services.AddOptions<OrderServiceOptions>().Configure(opt => { configuration.GetSection("OrderService").Bind(opt); })
.Services.AddSingleton<IValidateOptions<OrderServiceOptions>, OrderServiceValidateOptions>();

一般,当配置比较简单,且验证逻辑仅仅是对单个值的简单验证时,推荐DataAnnotations。

DataAnnotations不满足时,推荐验证函数,在设计组件的配置读取时推荐这种写法。

实现验证接口的方式,一般是验证逻辑较为复杂时,或验证逻辑依赖其它服务时。

-------------本文结束感谢您的阅读-------------