跟我一起學.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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM