目录
前言:
在 .NET Core 的配置系统中有主要有三个对象,它们分别是:IConfigurationSource(配置源对象)、IConfigurationBuilder(配置构建对象)、IConfiguration(配置对象)。还有一个也比较重要的是 IConfigurationProvider(配置提供对象)。
在实际执行过程中,配置构建对象利用注册在它上面的所有配置源对象提供的配置提供对象,来读取原始配置数据,并创建出配置对象。
贯穿全文的一个 Class:
public class Log
{
public bool Display {
get; set; }
public Level LogLevel {
get; set; }
public class Level
{
public string Default {
get; set; }
public string Microsoft {
get; set; }
}
}
一、结构化的配置
配置数据在程序中最终以 IConfiguration 的形式体现,数据来源可能是内存对象、物理文件、数据库等等,这个对象体现为一个树形逻辑结构,将每一个配置项的路径作为Key,那么每一个配置项都是一个Key-Value的形式,在系统中子节点与父节点的分隔符用 :
来表示。
那么我们直接看一段代码:
var source = new Dictionary<string, string>
{
["A:B:C"] = "ABC",
["A:B:D"] = "ABD",
};
IConfigurationRoot root = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build();
IConfigurationSection section1 = root.GetSection("A:B:C");
IConfigurationSection section2 = root.GetSection("A:B").GetSection("C");
IConfigurationSection section3 = root.GetSection("A").GetSection("B:C");
string section1Value = root["A:B:C"]; // ABC
string section2Value = root.GetSection("A:B")["C"]; // ABC
Debug.Assert(section1.Value == "ABC");
Debug.Assert(section2.Value == "ABC");
Debug.Assert(section3.Value == "ABC");
在当前代码中我们使用了内存配置源来建构配置对象,这里我们使用 A:B:C
,A:B:D
建构了一个 Y 形的树状数据结构,
我们可以直接使用 GetSection("A:B:C")
直接拿到最终的子节点,也可以先拿到子节点 GetSection("A:B")
然后从拿到的子节点去寻找最终的子节点 GetSection("C")
,
最后我们通过三个不同路径拿到的 Value 来判断是否都等于 ABC。
二、不同的配置源
首先我们引入 NuGet 包:
> # 输入命令从 NuGet 安装, 我用的NuGet版本为 Scrutor 3.1.3
> dotnet add package Microsoft.Extensions.Configuration.Xml
> dotnet add package Microsoft.Extensions.Configuration.Json
> dotnet add package Microsoft.Extensions.Configuration.CommandLine
> dotnet add package Microsoft.Extensions.Configuration.EnvironmentVariables
> dotnet add package Microsoft.Extensions.Options.ConfigurationExtensions
1) 基于内存的配置
基于内存的配置需要往配置构建对象上面注册内存配置源对象,
其中AddInMemoryCollection(source)
和Add(new MemoryConfigurationSource { InitialData = source })
是一样的效果。
我们用之前定义的 Log 类,用来承载内存配置。
var source = new Dictionary<string, string>
{
["Display"] = "true",
["LogLevel:Default"] = "Information",
["LogLevel:Microsoft"] = "Warning"
};
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(source)
// .Add(new MemoryConfigurationSource { InitialData = source })
.Build();
var log = configuration.Get<Log>();
执行代码可以发现,基于内存的值已经绑定在了 log 对象中。
2) 基于Json文件的配置
这里准备了一个 appsettings.json 文件:
{
"Display": true,
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning"
}
}
注册Json文件配置源对象时,
AddJsonFile("appsettings.json")
和 Add(new JsonConfigurationSource { Path = "appsettings.json" })
是一样的效果。
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
// .Add(new JsonConfigurationSource { Path = "appsettings.json" })
.Build();
var log = configuration.Get<Log>();
执行代码可以发现,基于Json文件的配置已经绑定在了 log 对象中。
3) 基于Xml文件的配置
这里准备了一个 appsettings.config 文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<Display>True</Display>
<LogLevel>
<Default>Warning</Default>
<Microsoft>Warning</Microsoft>
</LogLevel>
</configuration>
注册Json文件配置源对象时,
AddXmlFile("appsettings.config")
和 Add(new XmlConfigurationSource { Path = "appsettings.config" })
是一样的效果。
var configuration = new ConfigurationBuilder()
.AddXmlFile("appsettings.config")
// .Add(new XmlConfigurationSource { Path = "appsettings.config" })
.Build();
var log = configuration.Get<Log>();
执行代码可以发现,基于Xml文件的配置已经绑定在了 log 对象中。
4) 基于环境变量的配置
在使用环境变量配置时,AddEnvironmentVariables
方法还有一个重载是传一个字符串,该字符串作为环境变量的前缀,注册的环境变量名必须以其开头的前缀。而前缀将从环境变量名中删除。
Environment.SetEnvironmentVariable("App_Display", "true");
Environment.SetEnvironmentVariable("LogLevel:Default", "Warning");
Environment.SetEnvironmentVariable("LogLevel:Microsoft", "Warning");
var configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddEnvironmentVariables("App_")
.Build();
var java_home = configuration["JAVA_HOME"]; // D:\Other\java\jdk-14.0.2
var log = configuration.Get<Log>();
执行代码可以看到 java_home 拿到了 JAVA JDK 的环境变量,还拿到了并将内存中设置的环境变量绑定到了 log 对象中。
5) 基于命令行映射的配置
static void Main(string[] args)
{
// -d 是配置的简写映射, Display 是配置项名称(--Display /Display)
var mapping = new Dictionary<string, string>()
{
["-d"] = "Display",
["-ld"] = "LogLevel:Default",
["-lm"] = "LogLevel:Microsoft",
};
var configuration = new ConfigurationBuilder()
.AddCommandLine(args, mapping)
.Build();
var log = configuration.Get<Log>();
Console.WriteLine(log?.Display);
Console.WriteLine(log?.LogLevel?.Default);
Console.WriteLine(log?.LogLevel?.Microsoft);
}
编译成dll文件以后通过命令行执行,以下三句指令效果相同:
> dotnet .\File.ConsoleApp2.dll -d True -ld Warning -lm Warning
> dotnet .\File.ConsoleApp2.dll --Display True --LogLevel:Default Warning --LogLevel:Microsoft Warning
> dotnet .\File.ConsoleApp2.dll --Display True /LogLevel:Default Warning /LogLevel:Microsoft Warning
不加参数直接执行得到的结果将是 三行空白,通过上面某一句指令执行的结果是我们输入的值,说明我们输入的参数被成功绑定到了log对象上。
6) 其他
如果同时在配置构建对象中注册多个配置源对象:
var configuration = new ConfigurationBuilder()
.AddXmlFile("appsettings.config")
.AddJsonFile("appsettings.json")
.Build();
那么得到的结果将是后面的相同的配置将会覆盖前面的配置。
三、监控文件的变化
在将文件配置源对象注册到配置构建对象时,会有重载传参,其中有可选择文件是否可以不存在,还有是否监听文件的变化。
比如这里的AddJsonFile("appsettings.json", true, true)
第二个参数是文件可以不存在,第三个参数为要监听文件的变化。
static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
Read(config.Get<Log>());
ChangeToken.OnChange(() => config.GetReloadToken(), () =>
{
Read(config.Get<Log>());
});
Console.Read();
}
public static void Read(Log options)
{
Console.Clear();
Console.WriteLine($"Display=>{options.Display}");
Console.WriteLine($"LogLevel:Default=>{options.LogLevel.Default}");
Console.WriteLine($"LogLevel:Microsoft=>{options.LogLevel.Microsoft}");
}
在修改appsettings.json文件时,每次修改都能触发ChangeToken.OnChange
的第二个参数,即回调。