系列介紹
【五分鐘的dotnet】是一個利用您的碎片化時間來學習和豐富.net知識的博文系列。它所包含了.net體系中可能會涉及到的方方面面,好比C#的小細節,AspnetCore,微服務中的.net知識等等。html
經過本篇文章您將Get:json
- 不在AspNet Core的
Startup.cs
中完成mvc的選項配置(好比在其它地方爲MVC添加過濾器等操做) - 瞭解
Options
的使用 - 瞭解
IOptions
、IOptionsMonitor
、IOptionsSnapshot
的區別
時長爲五分鐘之內,建議先投幣再上車觀看😜mvc
正文
.NET Core爲我們提供的默認依賴注入方式[Microsoft.Extensions.DependencyInjection
]相對來講功能已經很完善了,雖然有一些功能沒有實現(好比在使用factory進行註冊時沒法獲取type等),但並不影響咱們令接口與實現進行分離。app
某些狀況下,您會發現,當咱們的業務類被添加到依賴注入容器中時,該類構造函數中所依賴的其它類都得一同添加到容器(雖然有某些奇技淫巧能夠規避,可是構造函數注入依舊是規範的手段)。但是,個人一些依賴類爲選型類型怎麼辦呢?好比下面的代碼:函數
public class MyBusinessClass { public MyBusinessClass(SomeOptions options) { if (options.ShouldOpenTCP) //do something..... if (options.ShouldLogIndo) // do something } }
SomeOptions
是一個典型的選項項類型,咱們經過它公開的一些屬性來對項目進行配置。而當MyBusinessClass
被注入到容器的時候,意味着SomeOptions
也須要被注入。微服務
對於這種選項類型,微軟給出了專門的處理手段:Microsoft.Extensions.Options
包。咱們只須要使用該包爲IServiceCollection
提供的擴展方法AddOptions<TOptions>()
就能夠完成注入選項:工具
services.AddOptions<SomeOptions>(); public class MyBusinessClass { public MyBusinessClass(IOptions<SomeOptions> options) { SomeOptions value = options.Value; } }
看起來這和上面的代碼好像區別也不是很大吧。都是把SomeOptions
添加到容器中,那麼第二種方法和第一種方法比起來有什麼優勢呢?微軟專門推出該方式難道只是爲了「年末衝業績」?post
非也非也?第二種方式其實用了更好的解耦思想來設計。假如我們的SomeOptions
須要在其它模塊中修改怎麼辦? 若是用第一種直接注入到容易的方案的話,這就十分的困難。而使用AddOptions<TOptions>
的方式您就能夠垂手可得。學習
Microsoft.Extensions.Options
提供了IConfigureOptions
和IPostConfigureOptions
這兩種相似於生命週期鉤子的接口,讓您可以在讀取選項的時候,進行某些操做。url
在AspNetCore中試一試
在AspnetCore中就有一個很明顯的選項:MvcOptions
,該選項提供了我們配置MVC項目的各類各樣的參數。
//Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddControllers(options => { options.Filters.Add(new MyFileter()); }); }
上面代碼是咱們在Startup.cs
中配置MvcOptions
最最多見的步驟,這裏我用添加一個全局過濾器來舉例。
若是我不想在Startup.cs
中添加這句代碼怎麼辦呢? 好比我寫了一個第三方的庫,庫中包含了N個過濾器,我確定沒有辦法要求用戶在使用該庫的時候將這N個過濾器一個一個的添加到options
中。(這裏只是假設,雖然可使用特性的方式來完成一樣的過濾器功能)
這個時候就能夠拿出咱們上面講的一大殺器:IConfigureOptions
.
internal class MvcOptionsConfigure : IConfigureOptions<MvcOptions> { public void Configure(MvcOptions options) { options.Filters.Add(new MyFileter()); } } services.AddSingleton<IConfigureOptions<MvcOptions>, MvcOptionsConfigure>();
這樣就完成了關注點的分離,咱們不須要一直死守着Startup.cs
文件不放,也不須要讓用戶手動去配置。只要咱們知道IServiceCollection
就能夠往裏面添加咱們本身的業務點。固然,Microsoft.Extensions.Options
包還提供了另外的方式讓您能夠完成IConfigureOptions
的一樣操做,不過這些操做都是像語法糖
同樣,實質上是相同的:
//和上面一樣的功能 services.Configure<MvcOptions>(Options => { options.Filters.Add(new MyFileter()); });
IOptions、IOptionsMonitor和IOptionsSnapshot
在上面其實咱們已經見過了IOptions
的尊容,咱們能夠經過注入IOptions<MyOptions>
來獲取MyOptions
實例。
可是!可是!可是!!!! IOptions
還有兩個兄弟IOptionsMonitor
和IOptionsSnapshot
。光名字上長的就很像了,它們都還有相似於「Value」的屬性來獲取選項實例。
媽呀,那麼它們到底有什麼不一樣呢?何時該用老大,什麼使用該用老二呢? 接下來,年度最佳找不一樣大戲即將開始………………
先來看看IOptions
和IOptionsSnapshot
吧,看看它們的接口定義:
/// <summary> /// Used to access the value of <typeparamref name="TOptions"/> for the lifetime of a request. /// </summary> /// <typeparam name="TOptions">Options type.</typeparam> public interface IOptionsSnapshot<out TOptions> : IOptions<TOptions> where TOptions : class, new() { /// <summary> /// Returns a configured <typeparamref name="TOptions"/> instance with the given name. /// </summary> TOptions Get(string name); }
我天,竟然IOptionsSnapshot
還繼承了IOptions
,並且只是多了一個Get
方法,那麼是否這兩個類其實很類似呢?咱們直接來看看源碼:
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>))); services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
納尼?這都還不是類似不類似的問題,這TM不是同一個實現嗎?只是接口類型不一樣而已,實現都是OptionsManager<>
。 那爲啥要搞兩個不一樣的接口。
等等(手動播放名偵探bgm),這倆生命週期咋不同? 一個是Singleton
一個是Scoped
。而再來看IOptionsSnapshot
的說明:「Used to access the value of TOptions for the lifetime of a request.」(用於在請求的生存期內訪問選項的值)。
原來如此,這樣看來就很清晰了。它倆的區別其實就是依賴注入的生命週期不一樣而已,爲單例的IOptions
意味着,只要您注入以後之後獲取的都是同一個實例,而IOptionsSnapshot
呢,做爲Scoped級別,每再一個新的Scoped中獲取它一次,它就會請求一個新的實例。
因此來舉個例子,在AspNet Core中我們某個選項的值是根據一個文件的某個值來的。剛開始文本的值是「A」,我們在運行AspNet Core以後咱們獲取IOptions<MyOptions>
和IOptionsSnapshot<MyOptions>
,此時獲得的MyOptions
的該屬性的值都是"A"。可是假如咱們更改了文本的值,改成「B」。若是在發起一個http請求去獲取MyOptions
的結果,此時IOptions<MyOptions>
依舊是「A」,而IOptionsSnapshot<MyOptions>
則更改成了B。
緣由很簡單,由於IOptions<MyOptions>
是單例的,因此從程序一開始加載過一次以後,之後訪問它都是這個結果,而IOptionsSnapshot<MyOptions>
是Scoped級別的,因此每個新的Scoped時都會又去訪問文本文件獲取值,而一次Http請求就會開啓一次新的Scoped,因此此時結果就成爲「B」。這個時候咱們大概就能讀懂上面IOptionsSnapshot<>
接口的解釋了:「用於在請求的生存期內訪問選項的值」。
三兄弟一下就幹掉了倆,接下來看看最後一個好兄弟(毒瘤):IOptionsMonitor
。仍是直接看它的源代碼呢:
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
納尼?單例? 那是否是意味着它也同樣,一旦啓動了以後仍是保持原有的結果呢?先不急,看看它的接口定義再說:
/// <summary> /// Used for notifications when <typeparamref name="TOptions"/> instances change. /// </summary> /// <typeparam name="TOptions">The options type.</typeparam> public interface IOptionsMonitor<out TOptions> { /// <summary> /// Returns the current <typeparamref name="TOptions"/> instance with the <see cref="Options.DefaultName"/>. /// </summary> TOptions CurrentValue { get; } /// <summary> /// Returns a configured <typeparamref name="TOptions"/> instance with the given name. /// </summary> TOptions Get(string name); /// <summary> /// Registers a listener to be called whenever a named <typeparamref name="TOptions"/> changes. /// </summary> /// <param name="listener">The action to be invoked when <typeparamref name="TOptions"/> has changed.</param> /// <returns>An <see cref="IDisposable"/> which should be disposed to stop listening for changes.</returns> IDisposable OnChange(Action<TOptions, string> listener); }
能夠看出它本身是一個單獨的接口,並不像其它倆兄弟是繼承關係。並且該接口竟然有一個OnChange
簽名?並且該方法須要一個Action<TOptions, string>
的參數。
握草(繼續手動播放名偵探bgm),若是您有幸看過個人上一篇文章:《【5min+】 一個令牌走天下!.Net Core中的ChangeToken》,那麼您可能一下就知道它扮演了什麼樣的角色。(5min+系列竟然是連續的.... 😭)
再看看該接口的說明:"Used for notifications when TOptions instances change."(用於在選項實例更改時進行通知)。果真和咱們猜的如出一轍,那麼它的實現類裏面必定有我們上一篇文章中提到的:ChangeToken
和IChangeToken
等東西。
來吧,扒開它的具體實現,驗證我們的猜測:
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); } }
意料之中,也就是說IOptionsMonitor<>
的注入級別雖然是單例,可是由於它具備IChangeToken
的實現,因此它可以在選項源改變的時候,「立馬對選項作出對應的改變」。而改變依賴於IOptionsChangeTokenSource
這個令牌源,目前.net core對不少經常使用工具都實現了該令牌源,好比Logger,Configuration等。因此當咱們某個選項依賴於IConfiguration
(appsetting.json)的某一項時,當修改appsetting.json文件,該選項的值就可以立馬獲得更改。
因此來回過頭來看這三兄弟。它們的區別其實在於變動的時效性:
類型 | 說明 | 時效性 |
---|---|---|
IOptions | 一旦程序啓動,該選項的值就沒法更改 | 無時效性可言 |
IOptionsSnapshot | 當開啓一個新Scoped時,就會從新計算選項的值 | 相對比較低,依賴於合適開啓一個新的Scoped |
IOptionsMonitor | 依賴於IChangeToken,只要令牌源變動則馬上作出反應 | 高 |
假如把IOptionsMonitor<MyOptions>
添加到上面IOptions<MyOptions>
和IOptionsSnapshot<MyOptions>
的文件變動案例,若是在一次HTTP請求中,文件變動了兩次,那麼IOptionsSnapshot<MyOptions>
不會在第二次更改中同步更改,而IOptionsMonitor<MyOptions>
則能夠。
那麼何時來使用什麼樣的接口呢?相信這個時候,您的內心比我還要清楚。當您的選項只是負責一次性處理的時候,應用啓動了就不須要更改,那麼考慮使用IOptions<MyOptions>
,若是是對數據源的變動要求很嚴格,好比開啓了一個「BackgroundJob」在後臺運行,該job須要一個選項類型,而該類型依賴於配置文件,須要對配置文件更改時即刻作出改變,那麼請考慮使用IOptionsMonitor<MyOptions>
。
最後回過頭來看微軟官方文檔上關於「Options」的兩個點(ISP和關注點分離),您應該一下就能理解。
若是您有興趣的話能夠跳轉至官方文檔進行閱讀:《ASP.NET Core 中的選項模式》
好啦,今天的車就開完了,若是您喜歡該系列文章能夠點擊關注。😝
最後,偷偷說一句:創做不易,點個推薦吧.....
原文出處:https://www.cnblogs.com/uoyo/p/12583149.html