zoukankan      html  css  js  c++  java
  • 跟我一起学.NetCore之选项(Options)核心类型简介

    前言

    .NetCore中提供的选项框架,我把其理解为配置组,主要是将服务中可供配置的项提取出来,封装成一个类型;从而服务可根据应用场景进行相关配置项的设置来满足需求,其中使用了依赖注入的形式,使得更加简单、便捷;另外和配置(Configuration)系统的无缝结合,使得服务更加灵活;而对于Options我们经常在注册服务中用到,相信只要接触过.NetCore中的小伙伴都知道,在注册服务的时候,经常在参数中进行Options的配置(如下图),可以直接的说:没有Options的服务不是好服务~~~

    img

    正文

    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综艺圈",识别关注跟我一起学~~~

    img

  • 相关阅读:
    关于《构建之法》读后感
    Lab04
    实验3_2
    实验3
    (1)写一个程序,用于分析一个字符串中各个单词出现的频率,并将单词和它出现的频率输出显示。(单词之间用空格隔开,如“Hello World My First Unit Test”); (2)编写单元测试进行测试; (3)用ElcEmma查看代码覆盖率,要求覆盖达到100%。
    1)把一个英语句子中的单词次序颠倒后输出。例如输入“how are you”,输出“you are how”; (2)编写单元测试进行测试; (3)用ElcEmma查看代码覆盖率,要求覆盖率达到100%
    个人简介
    平凡如我......
    第四次博客作业
    《构建之法》读后感
  • 原文地址:https://www.cnblogs.com/zoe-zyq/p/13539809.html
Copyright © 2011-2022 走看看