.Net Core 中選項(xiàng)Options的具體實(shí)現(xiàn)
.NetCore的配置選項(xiàng)建議結(jié)合在一起學(xué)習(xí),不了解.NetCore 配置Configuration的同學(xué)可以看下我的上一篇文章 [.Net Core配置Configuration具體實(shí)現(xiàn)]
由代碼開始
定義一個用戶配置選項(xiàng)
public class UserOptions { private string instanceId; private static int index = 0; public UserOptions() { instanceId = (++index).ToString("00"); Console.WriteLine($"Create UserOptions Instance:{instanceId}"); } public string Name { get; set; } public int Age { get; set; } public override string ToString() => $"Name:{Name} Age:{Age} Instance:{instanceId} "; } public class UserOptions2 { public string Name { get; set; } public int Age { get; set; } public override string ToString() => $" Name:{Name} Age:{Age}"; }
定義json配置文件:myconfig.json
{ "UserOption": { "Name": "ConfigName-zhangsan", "Age": 666 } }
創(chuàng)建ServiceCollection
services = new ServiceCollection(); var configBuilder = new ConfigurationBuilder().AddInMemoryCollection().AddJsonFile("myconfig.json", true, true); var iconfiguration = configBuilder.Build(); services.AddSingleton<IConfiguration>(iconfiguration);
示例代碼
services.Configure<UserOptions>(x => { x.Name = "張三"; x.Age = new Random().Next(1, 10000); }); services.AddOptions<UserOptions2>().Configure<IConfiguration>((x, config) => { x.Name = config["UserOption:Name"]; x.Age = 100; }); ; services.PostConfigure<UserOptions>(x => { x.Name = x.Name + "Post"; x.Age = x.Age; }); services.Configure<UserOptions>("default", x => { x.Name = "Default-張三"; x.Age = new Random().Next(1, 10000); }); services.Configure<UserOptions>("config", configuration.GetSection("UserOption")); using (var provider = services.BuildServiceProvider()) { using (var scope1 = provider.CreateScope()) { PrintOptions(scope1, "Scope1"); } //修改配置文件 Console.WriteLine(string.Empty); Console.WriteLine("修改配置文件"); var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "myconfig.json"); File.WriteAllText(filePath, "{\"UserOption\": { \"Name\": \"ConfigName-lisi\", \"Age\": 777}}"); //配置文件的change回調(diào)事件需要一定時間執(zhí)行 Thread.Sleep(300); Console.WriteLine(string.Empty); using (var scope2 = provider.CreateScope()) { PrintOptions(scope2, "Scope2"); } Console.WriteLine(string.Empty); using (var scope3 = provider.CreateScope()) { PrintOptions(scope3, "Scope3"); } } static void PrintOptions(IServiceScope scope, string scopeName) { var options1 = scope.ServiceProvider.GetService<IOptions<UserOptions>>(); Console.WriteLine($"手動注入讀取,IOptions,{scopeName}-----{ options1.Value}"); var options2 = scope.ServiceProvider.GetService<IOptionsSnapshot<UserOptions>>(); Console.WriteLine($"配置文件讀取,IOptionsSnapshot,{scopeName}-----{ options2.Value}"); var options3 = scope.ServiceProvider.GetService<IOptionsSnapshot<UserOptions>>(); Console.WriteLine($"配置文件根據(jù)名稱讀取,IOptionsSnapshot,{scopeName}-----{ options3.Get("config")}"); var options4 = scope.ServiceProvider.GetService<IOptionsMonitor<UserOptions>>(); Console.WriteLine($"配置文件讀取,IOptionsMonitor,{scopeName}-----{ options4.CurrentValue}"); var options5 = scope.ServiceProvider.GetService<IOptionsMonitor<UserOptions>>(); Console.WriteLine($"配置文件根據(jù)名稱讀取,IOptionsMonitor,{scopeName}-----{options5.Get("config")}"); var options6 = scope.ServiceProvider.GetService<IOptions<UserOptions2>>(); Console.WriteLine($"Options2-----{options6.Value}"); }
代碼運(yùn)行結(jié)果
Create UserOptions Instance:01
手動注入讀取,IOptions,Scope1----- Name:張三Post Age:6575 Instance:01
Create UserOptions Instance:02
配置文件讀取,IOptionsSnapshot,Scope1----- Name:張三Post Age:835 Instance:02
Create UserOptions Instance:03
配置文件根據(jù)名稱讀取,IOptionsSnapshot,Scope1----- Name:ConfigName-zhangsan Age:666 Instance:03
Create UserOptions Instance:04
配置文件讀取,IOptionsMonitor,Scope1----- Name:張三Post Age:1669 Instance:04
Create UserOptions Instance:05
配置文件根據(jù)名稱讀取,IOptionsMonitor,Scope1----- Name:ConfigName-zhangsan Age:666 Instance:05
Options2----- Name:ConfigName-zhangsan Age:100修改配置文件
Create UserOptions Instance:06手動注入讀取,IOptions,Scope2----- Name:張三Post Age:6575 Instance:01
Create UserOptions Instance:07
配置文件讀取,IOptionsSnapshot,Scope2----- Name:張三Post Age:5460 Instance:07
Create UserOptions Instance:08
配置文件根據(jù)名稱讀取,IOptionsSnapshot,Scope2----- Name:ConfigName-lisi Age:777 Instance:08
配置文件讀取,IOptionsMonitor,Scope2----- Name:張三Post Age:1669 Instance:04
配置文件根據(jù)名稱讀取,IOptionsMonitor,Scope2----- Name:ConfigName-lisi Age:777 Instance:06
Options2----- Name:ConfigName-zhangsan Age:100手動注入讀取,IOptions,Scope3----- Name:張三Post Age:6575 Instance:01
Create UserOptions Instance:09
配置文件讀取,IOptionsSnapshot,Scope3----- Name:張三Post Age:5038 Instance:09
Create UserOptions Instance:10
配置文件根據(jù)名稱讀取,IOptionsSnapshot,Scope3----- Name:ConfigName-lisi Age:777 Instance:10
配置文件讀取,IOptionsMonitor,Scope3----- Name:張三Post Age:1669 Instance:04
配置文件根據(jù)名稱讀取,IOptionsMonitor,Scope3----- Name:ConfigName-lisi Age:777 Instance:06
Options2----- Name:ConfigName-zhangsan Age:100
通過運(yùn)行代碼得到的結(jié)論
- Options可通過手動初始化配置項(xiàng)配置(可在配置時讀取依賴注入的對象)、或通過IConfiguration綁定配置
- PostConfiger可在Configer基礎(chǔ)上繼續(xù)配置
- 可通過IOptionsSnapshot或IOptionsMonitor根據(jù)配置名稱讀取配置項(xiàng),未指定名稱讀取第一個注入的配置
- IOptions和IOptionsMonitor生命周期為Singleton,IOptionsSnapshot生命周期為Scope
- IOptionsMonitor可監(jiān)聽到配置文件變動去動態(tài)更新配置項(xiàng)
問題
- IOptions,IOptionsSnapshot,IOptionsMonitor 如何/何時注入、初始化
- Options指定名稱時內(nèi)部是如何設(shè)置的
- Options如何綁定的IConfiguration
- IOptionsMonitor是如何同步配置文件變動的
配合源碼解決疑惑
Configure注入
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class { return services.Configure(Microsoft.Extensions.Options.Options.DefaultName, configureOptions); } public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions) where TOptions : class { services.AddOptions(); services.AddSingleton((IConfigureOptions<TOptions>)new ConfigureNamedOptions<TOptions>(name, configureOptions)); return services; } public static IServiceCollection AddOptions(this IServiceCollection services) { services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>))); services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>))); services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>))); services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>))); services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>))); return services; }
通過上面的源碼可以發(fā)現(xiàn),Options相關(guān)類是在AddOptions中注入的,具體的配置項(xiàng)在Configure中注入。
如果不指定Configure的Name,也會有個默認(rèn)的Name=Microsoft.Extensions.Options.Options.DefaultName
那么我們具體的配置項(xiàng)存到哪里去了呢,在ConfigureNamedOptions這個類中,在Configer函數(shù)調(diào)用時,只是把相關(guān)的配置委托存了起來:
public ConfigureNamedOptions(string name, Action<TOptions> action) { Name = name; Action = action; }
OptionsManager
private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(StringComparer.Ordinal); public TOptions Value => Get(Options.DefaultName); public virtual TOptions Get(string name) { name = name ?? Options.DefaultName; return _cache.GetOrAdd(name, () => _factory.Create(name)); }
OptionsManager實(shí)現(xiàn)相對較簡單,在查詢時需要執(zhí)行Name,如果為空就用默認(rèn)的Name,如果緩存沒有,就用Factory創(chuàng)建一個,否則就讀緩存中的選項(xiàng)。
IOptions和IOptionsSnapshot的實(shí)現(xiàn)類都是OptionsManager,只是生命周期不同。
OptionsFactory
那么OptionsFactory又是如何創(chuàng)建Options的呢?我們看一下他的構(gòu)造函數(shù),構(gòu)造函數(shù)將所有Configure和PostConfigure的初始化委托都通過構(gòu)造函數(shù)保存在內(nèi)部變量中
public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) { _setups = setups; _postConfigures = postConfigures; }
接下來看Create(有刪改,與本次研究無關(guān)的代碼沒有貼出來):
public TOptions Create(string name) { //首先創(chuàng)建對應(yīng)Options的實(shí)例 TOptions val = Activator.CreateInstance<TOptions>(); //循環(huán)所有的配置項(xiàng),依次執(zhí)行,如果對同一個Options配置了多次,最后一次的賦值生效 foreach (IConfigureOptions<TOptions> setup in _setups) { var configureNamedOptions = setup as IConfigureNamedOptions<TOptions>; if (configureNamedOptions != null) { //Configure中會判斷傳入Name的值與本身的Name值是否相同,不同則不執(zhí)行Action //這解釋了我們一開始的示例中,注入了三個UserOptions,但是在IOptionsSnapshot.Value中獲取到的是第一個沒有名字的 //因?yàn)閂alue會調(diào)用OptionsManager.Get(Options.DefaultName),進(jìn)而調(diào)用Factory的Create(Options.DefaultName) configureNamedOptions.Configure(name, val); } else if (name == Options.DefaultName) { setup.Configure(val); } } //PostConfigure沒啥可多說了,名字判斷邏輯與Configure一樣 foreach (var postConfigure in _postConfigures) { postConfigure.PostConfigure(name, val); } return val; }
NamedConfigureFromConfigurationOptions
IConfiguration配置Options的方式略有不同
對應(yīng)Configure擴(kuò)展方法最終調(diào)用的代碼在Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions這個類中
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder) where TOptions : class { services.AddOptions(); services.AddSingleton((IOptionsChangeTokenSource<TOptions>)new ConfigurationChangeTokenSource<TOptions>(name, config)); return services.AddSingleton((IConfigureOptions<TOptions>)new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder)); }
擴(kuò)展方法里又注入了一個IOptionsChangeTokenSource,這個類的作用是提供一個配置文件變動監(jiān)聽的Token
同時將IConfigureOptions實(shí)現(xiàn)類注冊成了NamedConfigureFromConfigurationOptions
NamedConfigureFromConfigurationOptions繼承了ConfigureNamedOptions,在構(gòu)造函數(shù)中用IConfiguration.Bind實(shí)現(xiàn)了生成Options的委托
public NamedConfigureFromConfigurationOptions(string name, IConfiguration config, Action<BinderOptions> configureBinder) : base(name, (Action<TOptions>)delegate(TOptions options) { config.Bind(options, configureBinder); })
所以在Factory的Create函數(shù)中,會調(diào)用IConfiguration的Bind函數(shù)
由于IOptionsSnapshot生命周期是Scope,在配置文件變動后新的Scope中會獲取最新的Options
ValidateOptions
OptionsBuilder還包含了一個Validate函數(shù),該函數(shù)要求傳入一個Func<TOptions,bool>的委托,會注入一個單例的ValidateOptions對象。
在OptionsFactory構(gòu)建Options的時候會驗(yàn)證Options的有效性,驗(yàn)證失敗會拋出OptionsValidationException異常
對于ValidateOptions和PostConfigureOptions都是構(gòu)建Options實(shí)例時需要用到的主要模塊,不過使用和內(nèi)部實(shí)現(xiàn)都較為簡單,應(yīng)用場景也不是很多,本文就不對這兩個類多做介紹了
結(jié)論
在Configure擴(kuò)展函數(shù)中會首先調(diào)用AddOptions函數(shù)
IOptions,IOptionsSnapshot,IOptionsMonitor都是在AddOptions函數(shù)中注入的
Configure配置的選項(xiàng)配置委托最終會保存到ConfigureNamedOptions或NamedConfigureFromConfigurationOptions
IOptions和IOptionsSnapshot的實(shí)現(xiàn)類為OptionsManager
OptionsManager通過OptionsFactory創(chuàng)建Options的實(shí)例,并會以Name作為鍵存到字典中緩存實(shí)例
OptionsFactory會通過反射創(chuàng)建Options的實(shí)例,并調(diào)用ConfigureNamedOptions中的委托給實(shí)例賦值
現(xiàn)在只剩下最后一個問題了,OptionsMonitor是如何動態(tài)更新選項(xiàng)的呢?
其實(shí)前面的講解中已經(jīng)提到了一個關(guān)鍵的接口IOptionsChangeTokenSource,這個接口提供一個IChangeToken,通過ChangeToken監(jiān)聽這個Token就可以監(jiān)聽到文件的變動,我們來看下OptionsMonitor是否是這樣做的吧!
//構(gòu)造函數(shù) public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache) { _factory = factory; _sources = sources; _cache = cache; //循環(huán)屬于TOptions的所有IChangeToken foreach (IOptionsChangeTokenSource<TOptions> source in _sources) { ChangeToken.OnChange(() => source.GetChangeToken(), delegate(string name) { //清除緩存 name = name ?? Options.DefaultName; _cache.TryRemove(name); }, source.Name); } } public virtual TOptions Get(string name) { name = name ?? Options.DefaultName; return _cache.GetOrAdd(name, () => _factory.Create(name)); }
果然是這樣的吧!
到此這篇關(guān)于.Net Core 中選項(xiàng)Options的具體實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān).Net Core Options內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
WPF數(shù)據(jù)綁定中的RelativeSource屬性
這篇文章介紹了WPF數(shù)據(jù)綁定中的RelativeSource屬性,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-04-04ASP.Net PlaceHolder、Panel等控件未實(shí)現(xiàn)INamingContainer,導(dǎo)致FindContro
這2天在開發(fā)中發(fā)現(xiàn),如果在new的Panel中使用FindControl,會出現(xiàn)找不到控件的情況2009-06-06微信公眾平臺開發(fā)之獲得ACCESSTOKEN .Net代碼解析
這篇文章主要為大家詳細(xì)解析了微信公眾平臺開發(fā)之獲得ACCESSTOKEN .Net代碼,感興趣的小伙伴們可以參考一下2016-06-06Asp.Net Core實(shí)現(xiàn)Excel導(dǎo)出功能的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Asp.Net Core實(shí)現(xiàn)Excel導(dǎo)出功能的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12ASP.NET Core中如何實(shí)現(xiàn)重定向詳解
這篇文章主要給大家介紹了關(guān)于ASP.NET Core中如何實(shí)現(xiàn)重定向的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01asp.net 產(chǎn)生隨機(jī)顏色實(shí)現(xiàn)代碼
asp.net 隨機(jī)顏色產(chǎn)生實(shí)現(xiàn)代碼,需要的朋友拿過去測試一下。2009-11-11.NET 6開發(fā)TodoList應(yīng)用之實(shí)現(xiàn)全局異常處理
因?yàn)樵陧?xiàng)目中,會有各種各樣的領(lǐng)域異?;蛳到y(tǒng)異常被拋出來,那么在Controller里就需要進(jìn)行完整的try-catch捕獲,并根據(jù)是否有異常拋出重新包裝返回值。有沒有辦法讓框架自己去做這件事呢?本文將為大家介紹如何實(shí)現(xiàn)全局異常處理,需要的可以參考一下2021-12-12asp.net實(shí)現(xiàn)C#繪制太極圖的方法
這篇文章主要介紹了asp.net實(shí)現(xiàn)C#繪制太極圖的方法,實(shí)例分析了asp.net繪制圖形的技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-02-02