ASP.NET Core的配置系统

前言

以下例子主要来源ASP.NET Core 3.1版本,后续版本可能会有所更改,请注意。

各版本区别既更多有关Configuration的信息,可以查看官方文档:Configuration in ASP.NET Core

关于ASP.NET Core的配置

ASP.NET Core 的配置遵循“约定大于配置”,是由一系列Configuration providers提供组合而成的。

ASP.NET Core Web应用,在建立引用服务前,会使用Host.CreateDefaultBuilder方法依次加载Configuration providers,顺序如下:

  1. ChainedConfigurationProvider:添加现有 IConfiguration 作为源。 在ASP.NET Core Web默认配置中,添加 Host 配置,并将它设置为应用配置的第一个源。
  2. 使用项目根目录下的appsettings.json
  3. 使用项目根目录下的appsettings.{Environment}.json 提供 。 例如,appsettings.Production.json 和 appsettings.Development.json 。
  4. 当在 Development 环境中运行时,会加载“用户机密”的配置。
  5. 使用系统环境变量的配置。
  6. 使用命令行参数的配置。

后来添加的配置会替代之前的配置。如:在appsettings.json中配置了"Name":"Joe",然后在命令行传了参数"Name":"Jack",那最终使用的就为后面的"Name":"Jack"

关于配置的key与value

有关Key

  • 不区分大小写。 例如,ConnectionStringconnectionstring 被视为等效键。

  • 如果在多个Configuration providers中设置了某一键和值,则会使用最后的Configuration providers中添加的值。

  • 采用扁平化处理,其分层键:

    如:

    1
    2
    3
    4
    5
    6
    {
    "User": {
    "Name": "Tina",
    "Age": 18
    }
    }

    最终会变成"User:Name":"Tina""User:Age":18这两项配置。

    • 在环境变量中,分隔符:可能无法适用于所有平台。 所有平台均支持采用双下划线 __,并且它在被程序读取后会自动转换为冒号 :
    • 在其他配置中,冒号分隔符 (:) 适用于所有平台。

有关Value

  • 储存为字符串类型。
  • NULL 值不能存储在配置中或绑定到对象。

Configuration providers

前面提到过ASP.NET Core中的配置是由一系列Configuration providers提供组合而成的,下面是其列表

providers 通过以下对象提供配置
Azure Key Vault configuration provider Azure Key Vault
Azure App configuration provider Azure 应用程序配置
Command-line configuration provider 命令行参数
Custom configuration provider 自定义源
Environment Variables configuration provider 环境变量
File configuration provider INI、JSON 和 XML 文件
Key-per-file configuration provider 目录文件
Memory configuration provider 集合,如:Dictionary<string, string>
User secrets 用户机密文件

具体要使用什么providers,请根据项目需要选择(点击对应的providers去查看),一般项目开发会用到命令行参数、JSON文件、及自定义源(如:数据库读取),用户机密文件,而生产环境需要用到JSON文件、及自定义源(如:数据库读取)、环境变量。

查看加载的所有配置项

方法一

Startup.Configure中添加以下代码,然后访问网站下的/dump-config地址,可以看到列出了所有的配置项,包含该项由什么provider提供、由那个文件提供,对于后续检查配置项的问题十分有用

1
2
3
4
5
6
7
8
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/dump-config", async ctx =>
{
var configInfo = (Configuration as IConfigurationRoot).GetDebugView();
await ctx.Response.WriteAsync(configInfo);
});
});

方法二

以下代码是我使用反射来获得的内容,和上面有点不同,上面微软提供的不符合要求可以参考以下我的

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
39
40
41
42
43
//获得配置项列表,按照provider分组
//objcect用来放provider,IDictionary<string, string>用来放该provider有的配置
static Dictionary<object, IDictionary<string, string>> GetConfigList(IConfigurationRoot conf)
{
IDictionary<object, IDictionary<string, string>> keyValuePairs = new Dictionary<object, IDictionary<string, string>>();

foreach (var confProvider in conf.Providers)
{
Type confProviderType = confProvider.GetType();
var fatherProviderType = confProviderType.BaseType.BaseType;

var datas = confProviderType.GetProperty("Data", BindingFlags.Instance | BindingFlags.NonPublic);
if (datas != null)
{
var configurationValue = (IDictionary<string, string>)datas.GetValue(confProvider);
if (configurationValue.Count > 0)
keyValuePairs.Add(confProvider, configurationValue);
}
}

return keyValuePairs;
}

//生成输出的 string
static string GetConfigViewStr(Dictionary<object, IDictionary<string, string>> keyValuePairs)
{
StringBuilder stringBuilder = new();
foreach (var pair in keyValuePairs)
{
stringBuilder.AppendLine($"****Provider : {pair.Key}****");
foreach (var keyValue in pair.Value)
{
stringBuilder.AppendLine($"Key = {keyValue.Key} : Value = {keyValue.Value}");
}
}
return stringBuilder.ToString();
}

app.MapGet("/dump-config", (RequestDelegate)(async ctx =>
{
var pair = GetConfigList((app.Configuration as IConfigurationRoot));
await ctx.Response.WriteAsync(GetConfigViewStr(pair));
}));

碎碎念

以下内容比较细碎,是一些不成体系内容,主要是为了帮助自己以后的回忆学习。

reloadOnChange

项目需要添加自己的 json 配置文件,可以使用下面方法

1
2
3
4
5
6
7
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(config => config.AddJsonFile("mysettings.json", optional: true, reloadOnChange: true))
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});

第3行内容就是在项目根目录读取一个名为mysettings.json的json文件获得配置,reloadOnChange: true表示在该json文件内容修改后,会自动在读取该文件加载新的配置内容。

reloadOnChange的实现是依靠PhysicalFileProvider的Watch方法,该方法会生成FileSystemWatcher对象来对这些文件或目录进行监控,针对这些文件或目录的变化(创建、修改、重命名和删除)都会实时地反映到Watch方法返回的ChangeToken上。

通过ChangeToken的RegisterChangeCallback方法可以注册一个回调方法,当监控的的对象出现变化时,便会执行预先注册的回调方法。

关于文件系统,可以看蒋金楠老师的https://www.cnblogs.com/artech/p/net-core-file-provider-03.html。

虽然reloadOnChange很方便,但是请注意使用的方法,不然可能导致FileSystemWatcher对象无法释放导致线程占有率过高问题。如该案例:记一次 .NET 某流媒体独角兽 API 句柄泄漏分析