NetCore中的选项框架
服务组件依赖选项框架
特性
- 支持单例模式读取配置
- 支持快照
- 支持配置变更通知
- 支持运行时动态修改选项值
在设计我们的系统时需要遵循两个原则
- 接口分离原则(ISP),我们的类不应该依赖它不使用的配置
- 关注点分离(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
29public 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>();
选项数据的热更新
- 关键类型:
- IOption<>是单例的,它不跟踪配置更新,读不到更新后的值。
- IOptionMonitor <>是单例的,它跟踪配置更新,总是读到最新的值。
- IOptionSnapshot<>是范围的,它在范围的生命周期中,不会更新,但它会读到范围生命周期创建前的变更。
- 使用IOptionsSnapshot进行热更新,还是之前的代码,只是将IOption换成IOptionsSnapshot,注意使用IOptionsSnapshot时,注册服务需要换成作用域模式
1 | private readonly IOptionsSnapshot<OrderServiceOptions> _options; |
- 使用IOptionsMonitor进行热更新和IOptionsSnapshot相同,但IOptionsMonitor可以适用于单例模式,而且在取值时用的是CurrentValue属性
1 | private readonly IOptionsMonitor<OrderServiceOptions> _options; |
定义一个扩展方法来操作我们OrderService的注册,并在扩展方法中动态配置我们的选项对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public 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 | //绑定选项并为选项添加验证逻辑Configure(opt => { configuration.GetSection("OrderService").Bind(opt); })就相当于上面那一句 |
- 实现DataAnnotations
1 | //使用属性验证的方式 |
- 使用IValidateOptions
1 | //定义一个验证类,实现IValidateOptions接口,该接口必需实现一个Validate方法,用来添加验证逻辑 |
一般,当配置比较简单,且验证逻辑仅仅是对单个值的简单验证时,推荐DataAnnotations。
DataAnnotations不满足时,推荐验证函数,在设计组件的配置读取时推荐这种写法。
实现验证接口的方式,一般是验证逻辑较为复杂时,或验证逻辑依赖其它服务时。