在 ASP.NET Core 中,选项模式可以简化配置的处理
选项模式使得代码更加整洁,可以避免在代码中进行大量的字符串解析和转换操作
先决条件
.NET 8 SDK
介绍
选项模式就是一种采用依赖注入来提供配置数据的编程方式
并不是直接利用依赖注入来提供配置对象,而是通过依赖注入提供选项对象来提供配置对象
选项对象有三种
代码准备
首先需要引入相关的 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); } }
复制代码
评论