zoukankan      html  css  js  c++  java
  • ASP.NET Core 2.1 源码学习之 Options[2]:IOptions

    上一章 中,介绍了Options的注册,而在使用时只需要注入 IOption<T> 即可:

    public ValuesController(IOptions<MyOptions> options)
    {
        var opt = options.Value;
    }
    

    本章就来详细介绍一下我们最熟悉的IOptions对象。

    目录

    1. IOptions
    2. OptionsManager
    3. OptionsFactory
    4. OptionsCache
    5. IOptionsSnapshot

    IOptions

    IOptions 定义非常简单,只有一个Value属性:

    public interface IOptions<out TOptions> where TOptions : class, new()
    {
        TOptions Value { get; }
    }
    

    上一章 中,我们知道它的默认实现为OptionsManager

        services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
    

    OptionsManager

    OptionsManager 对options的创建和获取进行管理,我们先看一下它的源码:

    public class OptionsManager<TOptions> : IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new()
    {
        private readonly IOptionsFactory<TOptions> _factory;
        private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>(); // Note: this is a private cache
    
        public OptionsManager(IOptionsFactory<TOptions> factory)
        {
            _factory = factory;
        }
    
        public TOptions Value => Get(Options.DefaultName);
    
        public virtual TOptions Get(string name) => _cache.GetOrAdd(name, () => _factory.Create(name ?? Options.DefaultName));
    }
    

    如上,OptionsManager 的实现非常简单,使用IOptionsFactory来创建options对象,并使用内部属性OptionsCache<TOptions> _cache进行缓存。

    OptionsFactory

    IOptionsFactory 只定义了一个Create方法:

    public interface IOptionsFactory<TOptions> where TOptions : class, new()
    {
        TOptions Create(string name);
    }
    

    其默认实现为:OptionsFactory

    public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
    {
        private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
        private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;
    
        public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures)
        {
            _setups = setups;
            _postConfigures = postConfigures;
        }
    
        public TOptions Create(string name)
        {
            var options = new TOptions();
            foreach (var setup in _setups)
            {
                if (setup is IConfigureNamedOptions<TOptions> namedSetup)
                {
                    namedSetup.Configure(name, options);
                }
                else if (name == Options.DefaultName)
                {
                    setup.Configure(options);
                }
            }
            foreach (var post in _postConfigures)
            {
                post.PostConfigure(name, options);
            }
            return options;
        }
    }
    

    OptionsFactory的构造函数中注入了IConfigureOptions<>IPostConfigureOptions<>,也就是我们在 上一章 中介绍的 Configure 方法中所注册的配置,而这里使用了 IEnumerable 类型,则表示当注册多个时,为按顺序依次执行。

    而我们在第一章中遇到的疑惑也豁然开朗,其Create方法,依次调用完Configure方法后,再调用PostConfigure方法,完成配置项的创建。

    OptionsCache

    OptionsCache 则是对字典的一个简单封装,就不再多说,直接看代码:

    public class OptionsCache<TOptions> : IOptionsMonitorCache<TOptions> where TOptions : class
    {
        private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(StringComparer.Ordinal);
    
        public void Clear() => _cache.Clear();
    
        public virtual TOptions GetOrAdd(string name, Func<TOptions> createOptions)
        {
            name = name ?? Options.DefaultName;
            return _cache.GetOrAdd(name, new Lazy<TOptions>(createOptions)).Value;
        }
    
        public virtual bool TryAdd(string name, TOptions options)
        {
            name = name ?? Options.DefaultName;
            return _cache.TryAdd(name, new Lazy<TOptions>(() => options));
        }
    
        public virtual bool TryRemove(string name)
        {
            name = name ?? Options.DefaultName;
            return _cache.TryRemove(name, out var ignored);
        }
    }
    

    IOptionsSnapshot

    最后再来介绍一下 IOptionsSnapshot :

    public interface IOptionsSnapshot<out TOptions> : IOptions<TOptions> where TOptions : class, new()
    {
        TOptions Get(string name);
    }
    

    看到Get方法的Name参数,我想大家便会想到在第一章中所介绍的指定NameConfigure方法,这便是它的用武之地了,通过Name的不同,可以配置同一个Options类型的多个实例。

    IOptionsSnapshot的实现与IOptions一样,都是OptionsManager

    services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
    services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
    

    通过如上代码,我们也可以发现它与IOptions还有一个本质的区别:IOptionsSnapshot注册的是Scoped类型,每一个请求中,都会创建一个新的OptionsManager对象。这样有什么好处呢?如果我们使用的是Action的方式配置的,那的确是没什么用,但是如果是使用IConfiguration配置的,则在我们修改了配置文件时,重新创建的Options会保持一致。

    总结

    本文描述了在 .NET Core Options 系统中IOptions的使用及实现原理。由于IOptions使用的是单例模式,因此当配置发生变化时,我们无法获取到最新的配置。而IOptionsSnapshot支持通过name来区分同一类型的不同 options ,并且每次请求都会重新创建 options 实例,相应的,会有稍微的性能损耗。如果我们希望能够监控配置源的变化,来自动更新,则可以使用下一章要介绍的更为强大的 IOptionsMonitor

  • 相关阅读:
    组合,封装与多态
    继承与派生
    面向对象基础练习
    面向对象基础
    类与对象
    数组与pandas模块
    Flask基础(15)-->模板代码的复用【宏(Macro)、继承(Block)、包含(include)】
    Flask基础(14)-->自定义过滤器
    Flask基础(13)-->Flask扩展Flask-Script
    Flask基础(12)-->请求上下文和应用上下文
  • 原文地址:https://www.cnblogs.com/RainingNight/p/strongly-typed-options-ioptions-in-asp-net-core.html
Copyright © 2011-2022 走看看