前言:
在 .NET Core 中的选项模式是必须通过依赖注入实现的一种模式,可以将应用的配置直接注入到所需的服务中。
选项模式就是一种采用依赖注入来提供配置数据的编程方式。并不是直接利用依赖注入来提供配置对象,而是通过依赖注入提供选项对象来提供配置对象。
选项对象有三种,IOptions<>
、IOptionsSnapshot<>
、IOptionsMonitor<>
,依次为基础的选项对象、快照选项对象、可监控的选项对象。
首先我们引入 NuGet 包:
> # 输入命令从 NuGet 安装, 我用的NuGet包的版本为 3.1.10
> dotnet add package Microsoft.Extensions.Configuration.CommandLine
> dotnet add package Microsoft.Extensions.Configuration.Json
> dotnet add package Microsoft.Extensions.DependencyInjection
> # 选项模式的配置扩展包
> dotnet add package Microsoft.Extensions.Options.ConfigurationExtensions
贯穿全文 Class:
public class Root
{
public Node FirstNode {
get; set; }
public Node SecondNode {
get; set; }
}
public class Node
{
public int Id {
get; set; }
public string Name {
get; set; }
}
还有一个名为appsettings.json的Json文件:
{
"FirstNode": {
"Id": 1, "Name": "First" },
"SecondNode": {
"Id": 2, "Name": "Second" }
}
一、关于IOptions<>的使用
IOptions<>
生命周期为Singleton
,没有数据热更新,读取的值永远不会变。
// 根据JSON配置文件构建一个配置对象
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var services = new ServiceCollection()
// 添加使用选项所需的服务
.AddOptions()
// 将配置对象和选项对象做一个映射
.Configure<Root>(configuration);
var serviceProvider = services.BuildServiceProvider();
IOptions<Root> options = serviceProvider.GetRequiredService<IOptions<Root>>();
Root root = options.Value;
以上代码中,首先使用JSON配置文件构建一个配置对象,
然后使用AddOptions()
将选项服务注册到服务注册对象,并使用Configure<Root>(configuration)
将配置对象和选项对象做一个映射。
后面是使用BuildServiceProvider()
建构服务提供对象(容器),然后从容器中获取对象,最后通过IOption<>.Value
拿到配置对象。
二、关于IOptionsSnapshot<>的使用
IOptionsSnapshot<>
是快照选项对象,生命周期为Scoped
,有数据热更新功能,在设置配置文件监控后,新的子容器获取的选项对象的配置会随着源文件的更改而更改。
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", false, true)
.Build();
// 两套配置,相同的类型 AppConfigDemo
var serviceProvider = new ServiceCollection()
.AddOptions()
// 给注册的选项命名,并传入到绑定的节点
.Configure<Root>(configuration)
.Configure<Node>("FirstNode", configuration.GetSection("FirstNode"))
.Configure<Node>("SecondNode", configuration.GetSection("SecondNode"))
.BuildServiceProvider();
// 没有命名的还是通过 Value 获取值
Root root = serviceProvider.GetRequiredService<IOptionsSnapshot<Root>>().Value;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IServiceProvider child = scope.ServiceProvider;
// 这时获取的不是 IOptions了,而是 IOptionsSnapshot 快照选项
IOptionsSnapshot<Node> options = child.GetRequiredService<IOptionsSnapshot<Node>>();
// 通过Get方法或者指定名称的配置
Node firstNode = options.Get("FirstNode");
Node secondNode = options.Get("SecondNode");
}
Console.ReadLine();
using (IServiceScope scope = serviceProvider.CreateScope())
{
IServiceProvider child = scope.ServiceProvider;
// 这时获取的不是 IOptions了,而是 IOptionsSnapshot 快照选项
IOptionsSnapshot<Node> options = child.GetRequiredService<IOptionsSnapshot<Node>>();
// 通过Get方法或者指定名称的配置
Node firstNode = options.Get("FirstNode");
Node secondNode = options.Get("SecondNode");
}
在使用IOptionsSnapshot<>
时,有些内容和IOptions<>
是一样的,
只不过获取的时候从GetRequiredService<IOptions<T>>
变成了GetRequiredService<IOptionsSnapshot<T>>()
。
并且可以给注册的选项命名,并传入要绑定的节点,例如这里将 FirstNode 和 SecondNode 都绑定到了 Node 这个配置对象,
在获取对象时,就变成了直接通过 Get("")
方法传入名称进行获取值。
在上面示例中,两个子容器之前用Console.ReadLine()
作为等待,这时去修改appsettings.json
文件后再往后执行,会发现新的子容器获取的值随着更改而改变。
三、关于IOptionsMonitor<>的使用
IOptionsMonitor<>
是可监控的选项对象,生命周期为Singleton
,有数据热更新功能,在设置配置文件监控后,新获取的选项对象的配置会随着源文件的更改而更改,并可以设置配置更改后的回调。
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", false, true)
.Build();
var serviceProvider = new ServiceCollection()
.AddOptions()
.Configure<Root>(configuration)
// 带监控的选项也具有给选项命名
.Configure<Node>("FirstNode", configuration.GetSection("FirstNode"))
.Configure<Node>("SecondNode", configuration.GetSection("SecondNode"))
.BuildServiceProvider();
var rootOptions = serviceProvider.GetRequiredService<IOptionsMonitor<Root>>();
var root = rootOptions.CurrentValue;
var nodeOptions = serviceProvider.GetRequiredService<IOptionsMonitor<Node>>();
var firstNode = nodeOptions.Get("FirstNode");
var secondNode = nodeOptions.Get("SecondNode");
// 发生变化以后的回调,可以拿到所变化配置节的名称configName
nodeOptions.OnChange((appConfig, configName) =>
{
Console.WriteLine($"Name:{appConfig.Id}");
Console.WriteLine($"Name:{appConfig.Name}");
Console.WriteLine($"Name:{appConfig.Id}");
Console.WriteLine($"Name:{appConfig.Name}");
});
Console.Read();
IOptionsMonitor<>
的内容基本上和IOptionsSnapshot<>
差不多,主要增加了OnChange
方法来设置配置更改以后的回调,还有就是生命周期的不同。
四、验证选项参数的合法性
在这里我们通过命令行映射的配置通过命令行传入参数,并手动赋值参数给选项对象,
然后通过Validate
判断传入的参数是否满足要求,并设置不满足要求时的错误提示。
如果满足,从容器中获取选项对象时将能成功运行,不能满足要求则会报错,并将我们设置的提示显示出来。
static void Main(string[] args)
{
var mapping = new Dictionary<string, string>() {
["-n"] = "Name", };
var config = new ConfigurationBuilder()
.AddCommandLine(args, mapping)
.Build();
var services = new ServiceCollection();
services.AddOptions<Node>()
.Configure(options => options.Name = config["Name"] ?? "")
.Validate(demo => demo.Name.EndsWith("Node"), "Name参数必须以Node结尾");
try
{
var options = services.BuildServiceProvider()
.GetRequiredService<IOptions<Node>>().Value;
Console.WriteLine(options.Name);
}
catch (OptionsValidationException ex)
{
Console.WriteLine(ex.Message);
}
}