跟我一起學.NetCore之選項(Options)核心型別簡介
前言
.NetCore中提供的選項框架,我把其理解為配置組,主要是將服務中可供配置的項提取出來,封裝成一個型別;從而服務可根據應用場景進行相關配置項的設定來滿足需求,其中使用了依賴注入的形式,使得更加簡單、便捷;另外和配置(Configuration)系統的無縫結合,使得服務更加靈活;而對於Options我們經常在註冊服務中用到,相信只要接觸過.NetCore中的小夥伴都知道,在註冊服務的時候,經常在引數中進行Options的配置(如下圖),可以直接的說:沒有Options的服務不是好服務~~~
正文
Options模型中主要有三個核心介面型別:IOption
**
-
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
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
**
**
本想看完以上預設實現,就打算進行舉例演示了,不再深入看程式碼;但是相信看到這的小夥伴肯定會問:IOptionsFactory
IOptionsFactory
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
----------------------------------------------
一個被程式搞醜的帥小夥,關注"Code綜藝圈",識別關注跟我一起學~~~