文章

.NET 配置框架

.NET 框架本身集成了强大的配置功能,支持多种配置源数据读写。

经常使用的 nuget 包:

1
2
3
4
5
6
Microsoft.Extensions.Configuration.Abstractions   //抽象包
Microsoft.Extensions.Configuration           //实现包
Microsoft.Extensions.Configuration.Binder      //强类型绑定
Microsoft.Extensions.Configuration.Json        //配置Json文件 
Microsoft.Extensions.Primitives           //配置验证 
Microsoft.Extensions.Options                     //配置选项

框架核心接口:

1
2
3
4
IConfigurationBuilder     //建造者接口
IConfigurationRoot        //配置根
IConfigurationSource    //配置源接口  
IConfigurationProvider  //配置提供程序接口

配置框架应用了建造者模式,我们需要将配置源提交给建造者,通过建造者接口提供的 build 方法,创建IConfigurationRoot 实例,它是配置的根,我们可以通过 IConfigurationRoot 实例访问到配置文件。通过实现 IConfigurationSourceIConfigurationProvider 两个接口,我们也可以在框架中添加自定义的配置源。

WebApi 项目代码示例

appsettings.json 中设置配置文件

1
2
3
4
5
6
7
{
  "key1": "nihao",
  "section1": {
    "key1": [ "1", "2" ],
    "key2":4
  }
}

构造函数中添加 IConfiguration 接口,并使用 _configuration 读取配置

1
2
3
4
5
6
7
8
9
10
11
12
13
private readonly IConfiguration _configuration; 
public WeatherForecastController(IConfiguration configuration)
{
      _configuration = configuration;
}

[HttpGet]
public string Get()
{
    Console.WriteLine(_configuration["key1"]);   //读取配置
    Console.WriteLine(_configuration.GetSection("section1")["key2"]);  
    return "OK";
}

因为 ASP.NET Core 框架已经默认添加了 appsettings.json 的配置源,所以我们直接使用 IConfiguration 接口就可以读取到其中的配置,如果我们需要添加新的配置文件则需要手动添加(注意:后添加的配置源,会覆盖掉与前面配置源相同 keyvalue 值。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration(builder => {
            builder.AddJsonFile("dbsettings.json", false, true); //添加新的配置源  optional:是否可选,为false时,启动时未找到该配置文件会程序异常。 reloadOnChange:配置变更时,是否重新加载配置。
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });
}

控制台程序实例

1
2
3
4
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("appsettings.json", false, true);
var iconfigurationRoot = configurationBuilder.Build();
Console.WriteLine($"key1 {iconfigurationRoot["key1"]}");

强类型绑定

配置框架支持将配置文件与类型绑定,它可以让我们使用配置更加方便,也使配置结构更加清晰。

创建一个 SectionOptions 类型

1
2
3
4
5
public class SectionOptions
{
    public string[] key1 { get; set; }
    public int key2 { get; set; } = 2;
}

ConfigureServices 方法中添加如下代码

1
2
3
4
5
6
7
8
var config = new SectionOptions()
{
};
Configuration.GetSection("section1").Bind(config, options =>  //将配置文件中的section1分段与config实例绑定
{
    options.BindNonPublicProperties = true; //支持私有属性绑定
});
services.AddSingleton<SectionOptions>(config);

这样我们就可以在构造函数中引入SectionOptions,并通过它来读写配置文件了。

配置变更监听

给配置文件变更事件设置回调函数,当配置文件更新时,我们可以在回调函数中,做相应的逻辑处理

1
2
3
4
5
6
7
ChangeToken.OnChange(() =>
{
    return Configuration.GetReloadToken();
}, () =>
{
    Console.WriteLine(Configuration["key1"]);
});

配置选项

配置选项模式让类型绑定变得更加简单、灵活。它支持三种模式:IOptionsIOptionsMonitorIOptionsSnapshot

ConfigureServices 中添加如下代码

1
services.Configure<SectionOptions>(Configuration.GetSection("section1"));

在构造函数中通过 IOptions 模式使用 SectionOptions ,它不能动态重载配置的变更

1
2
3
4
5
private readonly SectionOptions _sectionOptions; 
public WeatherForecastController(IOptions<SectionOptions> options)
{
    _sectionOptions = options.Value;
}

在构造函数中通过 IOptionsMonitor 模式使用 SectionOptions,它支持动态重载配置的变更,并保证每次读取的配置值都是最新的

1
2
3
4
5
private readonly SectionOptions _sectionOptions; 
public WeatherForecastController(IOptionsMonitor<SectionOptions> options)
{
    _sectionOptions = options.CurrentValue;
}

在构造函数中通过 IOptionsSnapshot 模式使用 SectionOptions,它支持动态重载配置的变更,但同一次请求域内,多次请求的值保持一致(可用来防止单次请求内由于配置文件变更引起的数据处理问题)

1
2
3
4
5
private readonly SectionOptions _sectionOptions; 
public WeatherForecastController(IOptionsSnapshot<SectionOptions> options)
{
    _sectionOptions = options.Value;
}

配置验证

配置框架还提供了三种配置验证的机制,当配置错误时,使用该配置程序会抛出异常。

DataAnnotations 特性验证,在模型中使用 DataAnnotations 特性标记  

1
2
3
services.AddOptions<SectionOptions>().Configure(option => {
            Configuration.GetSection("section1").Bind(option);
        }).ValidateDataAnnotations();

函数验证,在绑定配置时,直接注入验证函数

1
2
3
4
5
6
 services.AddOptions<SectionOptions>().Configure(option => {
            Configuration.GetSection("section1").Bind(option);
        }).Validate(section =>
        {
            return section.key2 > 1;
        });

实现 IValidateOptions 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SectionValidateOptions : IValidateOptions<SectionOptions>
{
    public ValidateOptionsResult Validate(string name, SectionOptions options)
    {
        if (options.key2 > 100)
        {
            return ValidateOptionsResult.Fail("key2 不能大于100");
        }
        else
        {
            return ValidateOptionsResult.Success;
        }    
    }
}
//在ConfigureServices方法中添加如下代码
 services.AddOptions<SectionOptions>().Configure(option => {Configuration.GetSection("section1").Bind(option);}).Services.AddSingleton<IValidateOptions<SectionOptions>, SectionValidateOptions>();

后配置处理

配置框架还提供了方法让我们对配置数据进行处理

1
2
3
4
services.PostConfigure<SectionOptions>(options =>
{
       options.key2 += 10;
});

自定义配置源

通过实现下面的两个接口,我们可以实现自己的配置提供程序,它可以用来获取远程配置中心的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class MyConfigurationSource : IConfigurationSource
{
    public IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        return new MyConfigurationProvider();
    }
}

public class MyConfigurationProvider :  ConfigurationProvider, IConfigurationProvider
{
    public MyConfigurationProvider()
    {
        var timer = new Timer();
        timer.Elapsed += Timer_Elapsed;
        timer.Interval = 3000;
        timer.Start();
    }

    private void Timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        Load(true);
    }

    public void Load()
    {
        Load(false);
    }

    private void Load(bool reload = false)
    {
        //可以在这里加载远程配置中心的配置
        this.Data["lastdatetime"] = $"{DateTime.Now}";
        if (reload)
        {
            base.OnReload();
        }
    }
}

添加自定义配置源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration(builder => {
        //    builder.Add(new MyConfigurationSource());  //添加自定义配置源
        })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

这样我们就可以通过配置框架获取到自定义配置源中的数据了。

本文由作者按照 CC BY 4.0 进行授权