前言
配置,对我们的程序来说是十分重要的一部分。或多或少都会写一部分内容到配置文件中去。
由其是在配置中心(Apollo等)做起来之前,配置文件一定会是我们的首选。
在.NET Core中,习惯的是用json文件当配置文件。读取的方法是不少,这里主要介绍的是用基于Options的方法来读,可以认为这是一种强类型的形式。
本文会介绍一些常见的用法,简单的单元测试示例,如果想探讨内部实现,请移步至雨夜朦胧的博客。
先来看看IOptions。
IOptions
先写好配置文件
{ ???"Demo": { ???????"Age": 18, ???????"Name": "catcher" ???}, ???//others ... }
然后定义对应的实体类
public class DemoOptions{ ???public int Age { get; set; } ???public string Name { get; set; }}
然后只需要在ConfigureServices方法添加一行代码就可以正常使用了。
public void ConfigureServices(IServiceCollection services){ ???services.Configure<DemoOptions>(Configuration.GetSection("Demo")); ???????//others..}
最后就是在想要读配置内容的地方使用IOptions去注入就好了。
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}";}
这个时候的结果,就会大致如下了:
这个时候可能会冒出这样的一个想法,如果某天,要修改某个配置项的值,它能及时生效吗?
口说无凭,来个动图见证一下。
事实证明,使用IOptions的时候,修改配置文件的值,并不会立刻生效!!
既然IOptions不行,那么我们就换一个!
下面来看看IOptionsSnapshot。
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没有区别,不同的时,它多了一个配置文件发生改变之后事件处理。
下面来看看。
IOptionsMonitor
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来进行调整。
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,就得根据场景来定了。
看看.NET Core几个Options的简单使用
原文地址:https://www.cnblogs.com/catcher1994/p/9190978.html