配置,對咱們的程序來講是十分重要的一部分。或多或少都會寫一部份內容到配置文件中去。json
由其是在配置中心(Apollo等)作起來以前,配置文件必定會是咱們的首選。api
在.NET Core中,習慣的是用json文件當配置文件。讀取的方法是很多,這裏主要介紹的是用基於Options的方法來讀,能夠認爲這是一種強類型的形式。app
本文會介紹一些常見的用法,簡單的單元測試示例,若是想探討內部實現,請移步至雨夜朦朧的博客。post
先來看看IOptions。單元測試
先寫好配置文件測試
{ "Demo": { "Age": 18, "Name": "catcher" }, //others ... }
而後定義對應的實體類this
public class DemoOptions { public int Age { get; set; } public string Name { get; set; } }
而後只須要在ConfigureServices方法添加一行代碼就能夠正常使用了。3d
public void ConfigureServices(IServiceCollection services) { services.Configure<DemoOptions>(Configuration.GetSection("Demo")); //others.. }
最後就是在想要讀配置內容的地方使用IOptions去注入就行了。code
private readonly DemoOptions _normal; public ValuesController(IOptions<DemoOptions> normalAcc) { this._normal = normalAcc.Value; } // GET api/values [HttpGet] public string Get() { var age = $"normal-[{_normal.Age}];"; var name = $"normal-[{_normal.Name}];"; return $"age:{age} \nname:{name}"; }
這個時候的結果,就會大體以下了:orm
這個時候可能會冒出這樣的一個想法,若是某天,要修改某個配置項的值,它能及時生效嗎?
口說無憑,來個動圖見證一下。
事實證實,使用IOptions的時候,修改配置文件的值,並不會馬上生效!!
既然IOptions不行,那麼咱們就換一個!
下面來看看IOptionsSnapshot。
對於Options家族,在Startup註冊的時候都是一個樣的,區別在於使用它們的時候。
private readonly DemoOptions _normal; private readonly DemoOptions _snapshot; public ValuesController(IOptions<DemoOptions> normalAcc, IOptionsSnapshot<DemoOptions> snapshotAcc) { this._normal = normalAcc.Value; this._snapshot = snapshotAcc.Value; } // GET api/values [HttpGet] public string Get() { var age = $"normal-[{_normal.Age}];snapshot-[{_snapshot.Age}];"; var name = $"normal-[{_normal.Name}];snapshot-[{_snapshot.Name}];"; return $"age:{age} \nname:{name}"; }
這個時候修改配置項的值以後,就會立馬更新了。
本質上,IOptions和IOptionsSnapshot是同樣的,只是他們註冊的生命週期不同,從而就有不一樣的表現結果。
固然,還有一個更強大的Options的存在,IOptionsMonitor。
它的用法和IOptionsSnapshot沒有區別,不一樣的時,它多了一個配置文件發生改變以後事件處理。
下面來看看。
private readonly DemoOptions _normal; private readonly DemoOptions _snapshot; private readonly DemoOptions _monitor; public ValuesController(IOptions<DemoOptions> normalAcc, IOptionsSnapshot<DemoOptions> snapshotAcc, IOptionsMonitor<DemoOptions> monitorAcc) { this._normal = normalAcc.Value; this._snapshot = snapshotAcc.Value; this._monitor = monitorAcc.CurrentValue; monitorAcc.OnChange(ChangeListener); } private void ChangeListener(DemoOptions options, string name) { Console.WriteLine(name); } // GET api/values [HttpGet] public string Get() { var age = $"normal-[{_normal.Age}];snapshot-[{_snapshot.Age}];monitor-[{_monitor.Age}];"; var name = $"normal-[{_normal.Name}];snapshot-[{_snapshot.Name}];monitor-[{_monitor.Name}];"; return $"age:{age} \nname:{name}"; }
效果和上面同樣的,不一樣的是,當保存appsettings.json
的時候,會觸發一次ChangeListener。
雖然說Snapshot和Monitor可讓咱們及時獲取到最新的配置項。
可是咱們也能夠經過PostConfigure或PostConfigureAll來進行調整。
public void ConfigureServices(IServiceCollection services) { services.Configure<DemoOptions>(Configuration.GetSection("Demo")); services.PostConfigureAll<DemoOptions>(x => { x.Age = 100; }); services.AddMvc(); }
若是咱們的代碼是這樣寫的,那麼,最終的結果就會是,不管咱們怎麼修改配置文件,最終展現的Age會一直是100。
你們也能夠思考一下這個能夠用在什麼場景。
隨便給你們看一段Steeltoe服務發現客戶端的代碼
private static void AddDiscoveryServices(IServiceCollection services, IConfiguration config, IDiscoveryLifecycle lifecycle) { var clientConfigsection = config.GetSection(EUREKA_PREFIX); int childCount = clientConfigsection.GetChildren().Count(); if (childCount > 0) { var clientSection = config.GetSection(EurekaClientOptions.EUREKA_CLIENT_CONFIGURATION_PREFIX); services.Configure<EurekaClientOptions>(clientSection); var instSection = config.GetSection(EurekaInstanceOptions.EUREKA_INSTANCE_CONFIGURATION_PREFIX); services.Configure<EurekaInstanceOptions>(instSection); services.PostConfigure<EurekaInstanceOptions>((options) => { EurekaPostConfigurer.UpdateConfiguration(config, options); }); AddEurekaServices(services, lifecycle); } else { throw new ArgumentException("Discovery client type UNKNOWN, check configuration"); } }
最後就是單元測試遇到Options要怎麼處理的問題了。
單元測試,這裏用了NSubstitute來做示例。
先簡單定義一些類
public class MyClass { private readonly MyOptions _options; public MyClass(IOptions<MyOptions> optionsAcc) { this._options = optionsAcc.Value; } public string Greet() { return $"Hello,{_options.Name}"; } } public class MyOptions { public string Name { get; set; } }
編寫測試類
public class MyClassTest { private readonly MyClass myClass; public MyClassTest() { var options = new MyOptions { Name = "catcher"}; var fake = Substitute.For<IOptions<MyOptions>>(); fake.Value.Returns(options); myClass = new MyClass(fake); } [Fact] public void GreetTest() { var res = myClass.Greet(); Assert.Equal("Hello,catcher", res); } }
重點在於fake了一下Options(這裏只以IOptions爲例),而後是告訴測試,若是有用到Value屬性的時候,就用返回定義好的Options。
也是比較簡單的作法,測試的結果天然也是符合預期的。
這幾個Options使用起來仍是比較順手的,至少什麼時候採用那種Options,就得根據場景來定了。