配置系统

.NET Core中提供了非常强大的配置系统以简化配置相关代码的编写方法。

配置系统的基本使用

.NET Core中的配置系统支持非常丰富的配置源,包括文件(JSON、XML、INI等)、注册表、环境变量、命令行、Azure Key Vault等,
配置系统还支持自定义配置源。
.NET Core中读取配置有很多种方式,既可以通过IConfigurationRoot读取配置,也可以使用绑定的方式把配置读取为一个C#对象。

在项目根目录下添加一个JSON文件,命名为:config.json

因为程序在运行的时候默认加载EXE文件同文件夹下的配置文件,而不是项目中的config.json文件,所以我们需要把config.json文件设置为生成项目的时候自动被复制到生成目录。

.NET Core中配置系统的基础开发包是

1
Microsoft.Extensions.Configuration,

而读取JSON文件的开发包是

1
Microsoft.Extensions.Configuration.Json

,用NuGet安装这两个包。

1
2
3
4
5
6
7
8
9
10
11
{
"name": "YOUXIANYU",
"age": "19",
{
"name": "Almango",
"proxy": {
"address": "255.255.255.255",
"port": "80"
}
}
}

收到读取代码

1
2
3
4
5
6
7
8
9
10
11
12
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;

ConfigurationBuilder configurationBuilder=new ConfigurationBuilder();
configurationBuilder.AddJsonFile("config.json",optional:false,reloadOnChange:true);
IConfigurationRoot configRoot= configurationBuilder.Build();
string name=configRoot["name"];
Console.WriteLine($"name={name}");
string add=configRoot.GetSection("proxy:address").Value;
Console.WriteLine($"address={add}");
Console.ReadKey();

绑定读取配置(*)
可以绑定一个类,自动完成配置的读取

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

ConfigurationBuilder configurationBuilder=new ConfigurationBuilder();
configurationBuilder.AddJsonFile("config.json",optional:false,reloadOnChange:true);
IConfigurationRoot configRoot= configurationBuilder.Build();
/*string name=configRoot["name"];
Console.WriteLine($"name={name}");
string add=configRoot.GetSection("proxy:address").Value;
Console.WriteLine($"address={add}");*/
/*Proxy proxy= configRoot.GetSection("proxy").Get<Proxy>();
Console.WriteLine($"{proxy.Address},{proxy.Port}");*/
Config config = configRoot.Get<Config>();
Console.WriteLine(config.Name);
Console.WriteLine(config.Proxy.Port);
Console.ReadKey();


class Config
{
public string Name { get; set; }
public int Add { get; set; }
public string Address { get; set; }
public Proxy Proxy { get; set; }
}
class Proxy
{
public string Address { get; set; }
public int Port { get; set; }
}

ConfigurationBuilder添加了一个待解析的配置文件。optional参数表示这个文件是否可选,如果它的值为trun,则当配置文件不存在的时候,程序不会报错;如果它的值为false,当配置文件不存在的时候,程序会报错。

IConfigurationRoot对象,我们能够通过他读取配置项,如果配置分级,也可以用“proxy:address”这种冒号分隔的方式读取配置项。

IConfigurationRoot中有一个GetConnectionString(string name)方法用于获取连接字符串,他读取“ConnectionStrings”节点下的名为name的值作为连接字符串。“ConnectionStrings”只是一个建议,不是.NET Core要求必须使用这个节点保存数据库连接字符串。

使用选项方式读取配置

使用选项方式读取配置是.NET Core中推荐的方式,因为他不及和依赖注入机制结合的更好,而且它可以实现配置修改后自动更新,用起来更方便。

使用选项方式读取,和DI结合更好,且更好利用“reloadonchange”机制。
读取配置的时候,DI要声明IOptions<T>、IOptionsMonitor<T>、IOptionsSnapshot<T>等类型。IOptions<T>不会读取到新的值;和IOptionsMonitor相比,IOptionsSnapshot会在同一个范围内(比如ASP.NET Core一个请求中)保持一致。建议用IOptionsSnapshot。

使用选项方式读取配置需要通过NuGet为项目安装

1
Microsoft.Extensions.Options

由于这种方式是对绑定方式的封装,因此我们任然需要同时安装包

1
Microsoft.Extensions.Configuration.Binder

在读取配置的地方,用IOptionsSnapshot<T>注入。不要在构造函数里直接读取IOptionsSnapshot.Value,而是到用到的地方在读取,否则无法更新变化。

1
2
3
4
5
6
7
8
9
10
11
12
{
"Logging": { "LogLevel": { "Default": "Warning" } },
"DB": {
"DbType": "SQLServre",
"ConnectionString": "Data Source=.;Initial Catalog=DemoDB; Integrated Security=Ture"
},
"Smtp": {
"Server": "youxianyu.cn",
"UserName": "zeng",
"Password": "123456"
},"AllowedHosts": "*"
}

读取“DB”和“Smtp”这两部分
建立对应配置项的两个模型类

1
2
3
4
5
6
7
8
9
10
11
public class DbSettings
{
public string DbType { get; set; }
public string ConnectionString { get; set; }
}
public class SmtpSettings
{
public string Server { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}

由于使用选项方式读取配置的时候,需要和依赖注入一起使用,因此我们需要创建一个类用于获取注入的选项值。
声明接收选项注入的对象的类型不能直接使用DbSettings、SmtpSettings,而要使用IOptions<T>、IOptionsMonitor<T>、IOptionsSnapshot<T>等泛型接口类型,可以帮我们处理容器生命周期、配置刷新等。
IOptions<T>在配置改变后,我们不能读到新的值,必须重启程序才可以读到新的值;
IOptionsMonitor<T>在配置改变后,我们能读到新的值;IOptionsSnapshot<T>也是在配置改变后,我们能读到新的值,和IOptionsMonitor<T>不同的是,在同一个范围内IOptionsMonitor<T>会保持一致性。

由于IOptions<T>不监听配置的改变,因此它的资源占用会比较少,适用与对服务启动后就不会改变的值进行读取。由于IOptionsMonitor<T>可能会导致用一个请求过程中,配置的改变使读取同一个选项的值不一致,从而导致程序出错,因此如果我们需要在程序运行中读取修改后的值,建议使用IOptionsSnapshot<T>。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Demo
{
private readonly IOptionsSnapshot<DbSettings> optDbSettings;
private readonly IOptionsSnapshot<SmtpSettings> optSmtpSettings;
public Demo(IOptionsSnapshot<DbSettings> optDbSettings, IOptionsSnapshot<SmtpSettings> optSmtpSettings)
{
this.optDbSettings = optDbSettings;
this.optSmtpSettings = optSmtpSettings;
}
public void Test()
{
var db=optDbSettings.Value;
Console.WriteLine($"数据库:{db.DbType},{db.ConnectionString}");
var smtp = optSmtpSettings.Value;
Console.WriteLine($"SMTP:{smtp.Server},{smtp.Username},{smtp.Password}");
}
}

这里,通过构造方法注入IOptionsSnapshot、IOptionsSnapshot两个服务,我们可以通过IOptionsSnapshot<T>的Value属性获取DbSettings、SmtpSettings等具体配置模型对象的值。

编写注入服务到容器的代码

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
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Configuration.Json;

using 选项方式读取配置_ID;

ConfigurationBuilder configBuilder= new ConfigurationBuilder();
configBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
IConfigurationRoot config = configBuilder.Build();
ServiceCollection services = new ServiceCollection();
services.AddOptions().Configure<DbSettings>(e=>config.GetSection("DB").Bind(e))
.Configure<SmtpSettings>(e=> config.GetSection("SMTP").Bind(e));
services.AddSingleton<Demo>();
using (var sp = services.BuildServiceProvider())
{
while (true)
{
using (var scope = sp.CreateScope())
{
var spScope = scope.ServiceProvider;
var demo = spScope.GetRequiredService<Demo>();
demo.Test();
}
Console.WriteLine("按任意键继续...");
Console.ReadKey();
}
}

在第2行代码中,把方法的reloadOnChange参数设定为true,以启动“修改后重新加载配置”的功能;在第5行代码中,通过AddOptions方法注册与选项相关的服务,然后使用第六行代码把DB节点的内容绑定到DbSettings类型的模型对象上。
由于IOptionsSnapshot<T>的生命周期为“范围”,因此Demo这个用与读取配置的类的生命周期不能是单例,我们在第8行代码中把Demo注册为瞬态服务。

其他配置提供者

命令行读取配置

配置框架还支持从命令行参数、环境变量等地方读取。
NuGet安装:

1
Microsoft.Extensions.Configuration.CommandLine

configBuilder.AddCommandLine(args)
参数支持多种格式,比如:server=127、–server=127.0.0.1、–server 127.0.0、(注意在键值之间加空格)、/server=127.0.0.1、/server 127.0.0.1(主要在键值之间加空格)。格式不能混用。

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
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.Configuration.CommandLine;

using 选项方式读取配置_ID;
ServiceCollection services = new ServiceCollection();
services.AddSingleton<Demo>();
ConfigurationBuilder configBuilder= new ConfigurationBuilder();

//configBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
configBuilder.AddCommandLine(args);
IConfigurationRoot config = configBuilder.Build();
services.AddOptions().Configure<DbSettings>(e=>config.GetSection("DB").Bind(e))
.Configure<SmtpSettings>(e=> config.GetSection("SMTP").Bind(e));
services.AddOptions().Configure<nameSettings>(e =>config.Bind(e));

using (var sp = services.BuildServiceProvider())
{
while (true)
{
using (var scope = sp.CreateScope())
{
var spScope = scope.ServiceProvider;
var demo = spScope.GetRequiredService<Demo>();
demo.Test();
}
Console.WriteLine("按任意键继续...");
Console.ReadKey();
}
}

在项目的属性的调试中设置命令行参数,这样Visual Studio中调试、运行程序的时候,Visual Studio会自动把这里设定的参数以命令行参数的形式传递给程序。

从环境变量读取配置

.NET Core中从环境变量读取配置需要安装:

1
Microsoft.Extensions.Configuration.EnvironmentVariables

然后调用AddEnvironmentVariables方法进行注册即可。AddEnvironmentVariables方法存在无参数和有prefix参数两个重载版本,无参数版本会将所有环境变量都加载进来,因此使用prefix参数的AddEnvironmentVartables重载方法进行注册。
prefix指的是环境变量名字的前缀。

1
2
3
4
5
ConfigurationBuilder configBuilder= new ConfigurationBuilder();
configBuilder.AddEnvironmentVariables("TEST_");
IConfigurationRoot configRoot=configBuilder.Build();
string name =configRoot["Name"];
Console.WriteLine(name);