跟我一起学.NetCore之选项(Options)核心类型简介
前言
.NetCore中提供的选项框架,我把其理解为配置组,主要是将服务中可供配置的项提取出来,封装成一个类型;从而服务可根据应用场景进行相关配置项的设置来满足需求,其中使用了依赖注入的形式,使得更加简单、便捷;另外和配置(Configuration)系统的无缝结合,使得服务更加灵活;而对于Options我们经常在注册服务中用到,相信只要接触过.NetCore中的小伙伴都知道,在注册服务的时候,经常在参数中进行Options的配置(如下图),可以直接的说:没有Options的服务不是好服务~~~
正文
Options模型中主要有三个核心接口类型:IOption、IOptionsSnapshot、IOptionsMonitor,这里的TOptions就是指针对服务提取出来的配置项封装的类型,以下分别看看三个核心类型定义了什么?**
**
IOption
namespace Microsoft.Extensions.Options
{
// 这里TOptions 做了一个约束,必须有无参构造函数
public interface IOptions<out TOptions> where TOptions : class, new()
{
// 这里是通过属性的方式定义TOptions
TOptions Value
{
get;
}
}
}
IOptionsSnapshot
namespace Microsoft.Extensions.Options
{ // 这里TOptions 做了一个约束,必须有无参构造函数
public interface IOptionsSnapshot<out TOptions> : IOptions<TOptions> where TOptions : class, new()
{
// 通过名字获取 TOptions
TOptions Get(string name);
}
}
IOptionsMonitor
namespace Microsoft.Extensions.Options
{
public interface IOptionsMonitor<out TOptions>
{
// 通过属性获取TOptions
TOptions CurrentValue
{
get;
}
// 通过名称获取TOptions
TOptions Get(string name);
// 这是用于监听改变的,如果数据设置项改变,就会发出通知
IDisposable OnChange(Action<TOptions, string> listener);
}
}
通过以上三种类型的定义,大概应该知道TOptions有对应的名字,根据对应的名字创建或获取TOptions,可能会问,IOption中是通过属性获取的,没有指定名字啊,其实是有的,只是名字默认为空,所以称之为默认Option;而对于IOptionsMonitor一看便知,它提供了监听改变的功能,所以后续如果需要监听改变,就可以用这个类型接口;除此,微软为三个核心类型提供了默认实现,IOptions和IOptionsSnapshot的默认实现为OptionsManager,IOptionsMonitor的默认实现为OptionsMonitor,来、走进他们的世界,看看是如何实现的(进行简单的注释):
OptionsManager
// 实现了IOptions<TOptions> 和IOptionsSnapshot<TOptions>, 同时也约束了TOptions
public class OptionsManager<TOptions> :IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new()
{
// 用于专门提供TOptions实例的,同时也对TOpions进行相关初始化
private readonly IOptionsFactory<TOptions> _factory;
// 提高性能,将对应的TOptions实例进行缓存
private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>();
// 构造函数,通过依赖注入的形式,将factory进行注入
public OptionsManager(IOptionsFactory<TOptions> factory)
{
_factory = factory;
}
// 实现IOptions<TOptions>通过属性获取TOptions实例
public TOptions Value
{
get
{
// 这里通过一个默认的名字获取,只是这个名字默认为空,所以还是有名字的
return Get(Options.DefaultName);
}
}
// 实现IOptionsSnapshot<TOptions>通过名字获取TOptions
public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName;
// 如果缓存中没有,就通过传入的Action进行创建并加入到缓存中
return _cache.GetOrAdd(name, () => _factory.Create(name));
}
}
// 定义的 TOptions默认名称
public static class Options
{
public static readonly string DefaultName = string.Empty;
}
OptionsMonitor
namespace Microsoft.Extensions.Options
{
// 实现IOptionsMonitor ,对TOpitons 进行约束
public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions>, IDisposable where TOptions : class, new()
{
// 根据名称缓存TOptions对象
private readonly IOptionsMonitorCache<TOptions> _cache;
// 用于创建TOptions对象
private readonly IOptionsFactory<TOptions> _factory;
// 监听改变的核心
private readonly IEnumerable<IOptionsChangeTokenSource<TOptions>> _sources;
private readonly List<IDisposable> _registrations = new List<IDisposable>();
// 改变触发的事件
internal event Action<TOptions, string> _onChange;
// 构造函数
public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
{
_factory = factory;
_sources = sources;
_cache = cache;
// 注册改变回调,用于监听
foreach (var source in _sources)
{
var registration = ChangeToken.OnChange(
() => source.GetChangeToken(),
(name) => InvokeChanged(name),
source.Name);
_registrations.Add(registration);
}
}
// 这个方法是重点,如果发生改变,移除之前的TOptions对象,重新创建一个新TOptions
private void InvokeChanged(string name)
{
name = name ?? Options.DefaultName;
// 根据名字移除TOpitons对象
_cache.TryRemove(name);
// 重新根据名称获取对象
var options = Get(name);
if (_onChange != null)
{
_onChange.Invoke(options, name);
}
}
// 获取默认的TOptions对象
public TOptions CurrentValue
{
get => Get(Options.DefaultName);
}
// 根据名称获取TOptions对象,如果没有就利用OptionsFactory创建TOptions对象
public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName;
//
return _cache.GetOrAdd(name, () => _factory.Create(name));
}
// 注册监听改变的蝙蝠
public IDisposable OnChange(Action<TOptions, string> listener)
{
var disposable = new ChangeTrackerDisposable(this, listener);
_onChange += disposable.OnChange;
return disposable;
}
// 取消注册的监听改变回调,同时移除对应的监听Token
public void Dispose()
{
foreach (var registration in _registrations)
{
registration.Dispose();
}
_registrations.Clear();
}
// 内部类
internal class ChangeTrackerDisposable : IDisposable
{
private readonly Action<TOptions, string> _listener;
private readonly OptionsMonitor<TOptions> _monitor;
public ChangeTrackerDisposable(OptionsMonitor<TOptions> monitor, Action<TOptions, string> listener)
{
_listener = listener;
_monitor = monitor;
}
// 触发改变时调用对应注册的回调
public void OnChange(TOptions options, string name) => _listener.Invoke(options, name);
// Dispose 方法进行解除注册的监听回调
public void Dispose() => _monitor._onChange -= OnChange;
}
}
}
通过以上代码段得知,IOptions和IOptionsSnapshot其实本质都是一样的,都是命名的Options,统一由IOptionsFactory创建提供TOptions对象;而对于IOptionsMonitor, 监听的本质是通过IOptionsChangeTokenSource这个实现,每次监听到改变都把对应名称的TOptions对象移除,重新创建一个新的TOptions对象,从而获取到最新的值,其实最终改变通知的本质还是使用IChangeToken进行通知,这个可以参考之前配置的监听改变(参考这篇:跟我一起学.NetCore之配置变更监听);
**
**
本想看完以上默认实现,就打算进行举例演示了,不再深入看代码;但是相信看到这的小伙伴肯定会问:IOptionsFactory是怎么创建出TOptions的? 重点都不说,差评_~~~~~,其实我也是这么想的,所以继续再看看IOptionsFactory;
IOptionsFactory的默认实现是OptionsFactory;创建TOptions我理解为三步骤,创建对象->加工对象(初始化)->验证(验证合法性):
namespace Microsoft.Extensions.Options
{
public interface IOptionsFactory<TOptions> where TOptions : class, new()
{
// 接口中定义了一个创建方法,用于创建TOptions
TOptions Create(string name);
}
}
namespace Microsoft.Extensions.Options
{
// 实现IOptionsFactory接口,并约束TOptions
public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{
// 初始化逻辑,初始化由IConfigureOptions和IPostConfigureOptions处理
private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;
// 验证逻辑
private readonly IEnumerable<IValidateOptions<TOptions>> _validations;
// 构造函数
public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) : this(setups, postConfigures, validations: null)
{ }
// 构造函数
public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<IValidateOptions<TOptions>> validations)
{
_setups = setups;
_postConfigures = postConfigures;
_validations = validations;
}
// 创建TOptions的核心方法,传入名称,如果没名称,默认是空
public TOptions Create(string name)
{
// 1 根据传入的TOptions创建对象,这里用无参构造函数,所以之前需要对TOptions进行约束
var options = new TOptions();
// 2 初始化
foreach (var setup in _setups)
{
// 根据传入的名字是否为默认名称选择不同的加工方法
if (setup is IConfigureNamedOptions<TOptions> namedSetup)
{
namedSetup.Configure(name, options);
}
else if (name == Options.DefaultName)
{
setup.Configure(options);
}
}
// IPostConfigureOptions对Options加工
foreach (var post in _postConfigures)
{
post.PostConfigure(name, options);
}
// 进行验证, 如果不传入验证规则,则代表不进行验证
if (_validations != null)
{
// 存放验证失败的错误消息
var failures = new List<string>();
// 遍历验证
foreach (var validate in _validations)
{
// 进行验证
var result = validate.Validate(name, options);
// 如果验证失败
if (result.Failed)
{
// 将验证失败错误信息加入到列表中
failures.AddRange(result.Failures);
}
}
// 如果验证失败,就抛出异常OptionsValidationException
if (failures.Count > 0)
{
throw new OptionsValidationException(name, typeof(TOptions), failures);
}
}
// 返回实例对象
return options;
}
}
}
对于TOptions的创建逻辑就暂时先看到这吧,如果需要再详细了解具体逻辑,可以私下进行研究;
总结
哎呀,这篇先不一一举例演示了,可能导致篇幅过长,上个WC的时间估计看不完(哈哈哈);那么就是单纯的代码说明吗?不仅仅如此,这篇主要讲解代码的同时,其实着重凸显了IOption、IOptionsSnapshot、IOptionsMonitor三个核心类型,然后围绕三个核心类型简单看了内部实现、创建过程和监听逻辑,因为在实际应用也是围绕以上进行使用和扩展的,最终的目的是让使用不再糊涂,其实这才是终极目标啦~~~~ 下一篇就专门针对Options举例演示!!!
----------------------------------------------
一个被程序搞丑的帅小伙,关注"Code综艺圈",识别关注跟我一起学~~~
跟我一起学.NetCore之选项(Options)核心类型简介的更多相关文章
- 跟我一起学.NetCore之日志(Log)模型核心
前言 鲁迅都说:没有日志的系统不能上线(鲁迅说:这句我没说过,但是在理)!日志对于一个系统而言,特别重要,不管是用于事务审计,还是用于系统排错,还是用于安全追踪.....都扮演了很重要的角色:之前有很 ...
- 跟我一起学.NetCore之静态文件处理的那些事
前言 如今前后端分离开发模式如火如荼,开发职责更加分明(当然前后端一起搞的模式也没有完全褪去):而对于每个公司产品实施来说,部署模式会稍有差别,有的会单独将前端文件部署为一个站点,有的会将前端文件和后 ...
- 跟我一起学.NetCore之文件系统应用及核心浅析
前言 在开发过程中,肯定避免不了读取文件操作,比如读取配置文件.上传和下载文件.Web中html.js.css.图片等静态资源的访问:在配置文件读取章节中有说到,针对不同配置源数据读取由对应的ICon ...
- 跟我一起学.NetCore之中间件(Middleware)简介和解析请求管道构建
前言 中间件(Middleware)对于Asp.NetCore项目来说,不能说重要,而是不能缺少,因为Asp.NetCore的请求管道就是通过一系列的中间件组成的:在服务器接收到请求之后,请求会经过请 ...
- 跟我一起学.NetCore之WebApi接口裸奔有风险(Jwt)
前言 撸码需谨慎,裸奔有风险.经常在一些技术交流群中了解到,还有很多小伙伴的项目中Api接口没有做任何安全机制验证,直接就裸奔了,对于一些临时项目或是个人小项目还好,其余的话,建议小伙伴们酌情考虑都加 ...
- 跟我一起学.NetCore之熟悉的接口权限验证不能少(Jwt)
前言 权限管控对于一个系统来说是非常重要的,最熟悉不过的是菜单权限和数据权限,上一节通过Jwt实现了认证,接下来用它实现接口权限的验证,为什么不是菜单权限呢?对于前后端分离而言,称其为接口权限感觉比较 ...
- 跟我一起学.NetCore之MVC过滤器,这篇看完走路可以仰着头走
前言 MVC过滤器在之前Asp.Net的时候就已经广泛使用啦,不管是面试还是工作,总有一个考点或是需求涉及到,可以毫不疑问的说,这个技术点是非常重要的: 在之前参与的面试中,得知很多小伙伴只知道有一两 ...
- 跟我一起学.NetCore之日志作用域及第三方日志框架扩展
前言 上一节对日志的部分核心类型进行简单的剖析,相信现在再使用日志的时候,应该大概知道怎么一回事了,比如记录器是怎么来的,是如何将日志内容写入到不同目的地的等:当然还有很多细节没深入讲解,抽时间小伙伴 ...
- 跟我一起学.NetCore之中间件(Middleware)应用和自定义
前言 Asp.NetCore中的请求管道是通过一系列的中间件组成的,使得请求会根据需求进行对应的过滤和加工处理.在平时开发中会时常引用别人定义好的中间件,只需简单进行app.Usexxx就能完成中间件 ...
随机推荐
- Ansible基础
Ansible基于Python paramiko 开发,分布式,无需客户端,轻量级,配置语法使用YMAL 及Jinja2模板语言. 组件: 核心:ansible 核心模块(Core Modules): ...
- 前端网(http://www.qdfuns.com/)不能访问了
前端网(http://www.qdfuns.com/)不能访问了 之前写的一些知识点也找不到了,有点难受.... 这说明知识点还是放在本地电脑稳一点,多备份,云端时刻在变化... 希望博客园别也用着用 ...
- Python time sleep()方法
描述 Python time sleep() 函数推迟调用线程的运行,可通过参数secs指秒数,表示进程挂起的时间.高佣联盟 www.cgewang.com 语法 sleep()方法语法: time. ...
- Canvas知识点补充
Canvas笔记 复习 初识canvas <canvas> 是 HTML5 新增的,一个可以使用脚本(通常为 JavaScript) 在其中绘制图像的 HTML 元素.它可以用来制作照片集 ...
- 代码规范、API设计等规范
一份整理好了的规范文档,node后端开发用到 "规范是个好东西..." - 鲁迅 以下规范仅作为参考 1.代码规范 命名 尽量保证命名更加语义化 文件命名采用下划线命名法 // g ...
- JAVA设计模式 5【结构型】代理模式的理解与使用
今天要开始我们结构型 设计模式的学习,设计模式源于生活,还是希望能通过生活中的一些小栗子去理解学习它,而不是为了学习而学习这些东西. 结构型设计模式 结构型设计模式又分为 类 结构型 对象 结构型 前 ...
- 使用Flask开发简单接口(1)--GET请求接口
前言 很多想学习接口测试的同学,可能在最开始的时候,常常会因没有可以练习的项目而苦恼,毕竟网上可以练习的接口项目不多,有些可能太简单了,有些可能又太复杂了,或者是网上一些免费接口请求次数有限制,最终导 ...
- cocos2d-x_下载游戏引擎并创建第一个项目
我是一名小白. 下载并创建游戏项目 第一步:去官网下载cocos2d-x http://www.cocos.com/download 第二步:将安装包里边的 setup.py 拖进命令行点击回车键 , ...
- 开源丨CloudBase CMS 内容管理系统!简单易用企业内容管理流
背景 云开发CloudBase CMS 是云开发推出的一站式云端内容管理系统,助力企业的数据运营管理工作. 开发者可以直接在云开发扩展能力中一键安装 CloudBase CMS,免费使用 CloudB ...
- 2020-07-20:你觉得redis有什么缺点,给你改进的话你会怎么改进?
福哥答案2020-07-20: 1.由于 Redis 是内存数据库,短时间内大量增加数据,可能导致内存不够用.2.redis是单线程的,单台服务器无法充分利用多核服务器的CPU.3.遇到大量查询时容易 ...