关于 .NET Core 中的选项模式

前言:

在 .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);
        }
    }

猜你喜欢

转载自blog.csdn.net/Upgrader/article/details/111603030
今日推荐