前言

    在之前的文章.Net Core Configuration源码探究一文中我们曾解读过Configuration的工作原理,也.Net Core Configuration Etcd数据源一文中探讨过为Configuration自定义数据源需要哪些操作。由于Configuration配置系统也是.Net Core的核心,其中也包含了许多细节,其中通过启动命令行CommandLine、环境变量、配置文件或定义其他数据源的形式,其实都是适配到配置系统中,我们都可以通过Configuration去读取它们的数据,但是在程序默认的情况下他们读取的优先级到底是怎么样的呢?接下来我们就一起来研究一下。

代码演示

由于Configuration数据操作是我们实操代码过程中不可或缺的环节,所以我们先通过代码的形式来看一下,它的读取顺序到底是什么样子的,首先我们建立一个示例,在这个示例中我们分别在常用配置数据的地方,CommandLine、环境变量、appsettings.json、ConfigureWebHostDefaults中的UseSetting和ConfigureAppConfiguration中读取自定义的文件mysettings.json中分别设置一个同名的配置节点叫FromSource,然后它的值设置FromSource节点的数据来自于哪个配置方式,比如环境变量中我配置的是Environment

"MyDemo.Config": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://localhost:19573",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"FromSource": "Environment"
}

配置文件中我配置的是appsetting.json

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"FromSource": "appsetting.json"
}

自定义的配置文件中我配置的是mysettings.json

{
"FromSource": "mysetting.json"
}

然后在启动程序Program.cs中配置如下

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(config => {
config.AddJsonFile("mysettings.json", optional: true, reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseSetting("FromSource", "UseSetting");
webBuilder.UseStartup<Startup>();
});

为了方便演示我们在程序的默认终结点中添加响应的读取代码

app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync($"Read Node FromSource={Configration["FromSource"]}");
});
});

以上操作我们都完成了配置后,然后通过CLI的方式启动程序并传递--FromSource=CommandLine

dotnet run --FromSource=CommandLine

程序运行起来之后输入host+port的形式请求默认路径得到的结果是

Read Node FromSource=mysetting.json

说明默认情况下优先级最高的是通过ConfigureAppConfiguration方法注册自定义配置,然后我们注释掉设置读取mysetting.json数据源的相关代码,然后继续运行程序,得到的结果是

Read Node FromSource=CommandLine

这个是通过CLI启动程序我们手动传递的命令行参数,然后我们退出程序,再次通过CLI的方式运行程序,但是这次我们不传递--FromSource=CommandLine,得到的结果是

Read Node FromSource=Environment

这是我们在环境变量中配置的节点数据,然后我们注释掉在环境变量中配置的节点数据,再次启动程序得到的结果是

Read Node FromSource=appsetting.json

也就是我们在默认配置文件中appsetting.json配置的数据,然后我们注释掉这个数据节点,继续运行程序,毫无疑问得到的结果是

Read Node FromSource=UseSetting

通过这个演示结果我们可以得到这么一个结论,在Asp.Net Core中如果你采用的是系统默认的形式构建的程序,那么读取配置节点的优先级是ConfigureAppConfiguration(自定义读取)>CommandLine(命令行参数)>Environment(环境变量)>appsetting.json(默认配置文件)>UseSetting的顺序。

源码探究

要想知道,为什么演示示例会出现那种顺序,还要从源码着手。在之前的.Net Core Configuration源码探究中我们提到过Configuration读取数据的顺序采用的是后来者居上的形式,也就是说,后被注册的ConfigurationProvider中的数据会优先被读取到,这个操作处理在ConfigurationRoot类中可以找到相关逻辑[点击查看源码],它的实现是这样的

public string this[string key]
{
get
{
//通过这个我们可以了解到读取的顺序取决于注册Source的顺序,采用的是后来者居上的方式
//后注册的会先被读取到,如果读取到直接return
for (var i = _providers.Count - 1; i >= 0; i--)
{
var provider = _providers[i];
if (provider.TryGet(key, out var value))
{
return value;
}
}
return null;
}
set
{
if (!_providers.Any())
{
throw new InvalidOperationException(Resources.Error_NoSources);
}
//这里的设置只是把值放到内存中去,并不会持久化到相关数据源
foreach (var provider in _providers)
{
provider.Set(key, value);
}
}
}

通过这段代码我们就心理就有底了,也就是说,上面示例表现出来的现象,无非就是注册顺序的问题。

默认的CreateDefaultBuilder

默认情况下我们都是通过Host.CreateDefaultBuilder(args)的方式去构建的HostBuilder,那么我们就从这个方法入手,找到源码位置,我们抽离出关于配置操作的逻辑,大致如下

public static IHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new HostBuilder();
//配置默认内容根目录为当前程序运行目录
builder.UseContentRoot(Directory.GetCurrentDirectory());
//配置HostConfiguration,这个地方不要被吓到,最终通过HostConfiguration配置的操作都是要加载到ConfigureAppConfiguration里的
//至于如何加载,待会我们会通过源码看到
builder.ConfigureHostConfiguration(config =>
{
//先配置环境变量
config.AddEnvironmentVariables(prefix: "DOTNET_");
//然后配置命令行读取
if (args != null)
{
config.AddCommandLine(args);
}
}); builder.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
//首先添加的就是读取appsettings.json相关
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
//添加环境变量配置读取相关
config.AddEnvironmentVariables();
//启动时命令行参数不为null则添加CommandLine读取
if (args != null)
{
config.AddCommandLine(args);
}
})
//*其他部分逻辑已省略,有兴趣可自行点击上方连接查看源码
return builder;
}

通过CreateDefaultBuilder我们可以非常清晰的得到这个结论由于先注册的是读取appsettings.json相关的逻辑,然后是AddEnvironmentVariables去读取环境变量,最后是AddCommandLine读取命令行参数加载到Configuration中,所以通过这个我们验证了优先级CommandLine(命令行参数)>Environment(环境变量)>appsetting.json(默认配置文件)的顺序。

ConfigureAppConfiguration中寻找答案

通过上面CreateDefaultBuilder我们得到了Configuration默认读取优先级的一部分逻辑认证,但是在示例的演示中,我们清楚的看到ConfigureAppConfiguration中配置的读取优先级是大于以上任何一个读取方式的,所以接下来我们还得需要到ConfigureAppConfiguration方法中一探究竟,这是一个扩展方法,默认调用的是HostBuilder中的ConfigureAppConfiguration方法[点击查看源码]

public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
{
_configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}

_configureAppConfigActions是HostBuilder的私有属性

private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>();

也就是说我们通过ConfigureAppConfiguration实现的逻辑都会被添加到_configureAppConfigActions这个List中,但是这个还不是我们要查找的核心。看来我们要去HostBuilder.Build()方法找寻找答案了,毕竟真正的构建逻辑还是在Build方法中,最后我们找到了如下方法[点击查看源码]

private void BuildAppConfiguration()
{
//用默认的ContentRootPath去构建一个全局的ConfigurationBuilder
var configBuilder = new ConfigurationBuilder()
.SetBasePath(_hostingEnvironment.ContentRootPath)
//首先就是把通过ConfigureHostConfiguration配置的相关添加到ConfigurationBuilder中
.AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true);
//通过循环的方式去执行我们注册到_configureAppConfigActions集合中的逻辑
foreach (var buildAction in _configureAppConfigActions)
{
buildAction(_hostBuilderContext, configBuilder);
}
_appConfiguration = configBuilder.Build();
_hostBuilderContext.Configuration = _appConfiguration;
}

由于_configureAppConfigActions是被循环执行的,也就是说先被注册到ConfigureAppConfiguration中的逻辑也是优先被执行,那么我们在CreateDefaultBuilder方法中,系统默认给我注册的AddJsonFile、AddEnvironmentVariables、AddCommandLine的调用顺序要优先于我们自行通过ConfigureAppConfiguration注册配置的逻辑。由于Configuration读取数据的顺序采用的是后来者居上的形式,所以我们自行通过ConfigureAppConfiguration注册的配置逻辑优先级是大于系统默认给我们注册读取配置的优先级。因此通过这些我们可以得到了这个结论ConfigureAppConfiguration(自定义读取)>CommandLine(命令行参数)>Environment(环境变量)>appsetting.json(默认配置文件)。除此之外还可以得到一个结论,默认情况下通过ConfigureHostConfiguration添加的配置相关,优先级是最低的。因为在循环执行_configureAppConfigActions循环之前,也就是在构建ConfigurationBuilder的时候就添加了ConfigureHostConfiguration。

UseSetting最后的迷雾

通过上面的相关源码我们已经得到了,关于默认配置读取优先级的大部分实现逻辑,仅仅剩下通过ConfigureWebHostDefaults中添加的UseSetting相关逻辑。可能有许多同学不清楚,其实UseSetting也是添加到配置系统当中去的,这个可以查看具体源码[点击查看源码]

private IConfiguration _config = new ConfigurationBuilder()
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
.Build();
public IWebHostBuilder UseSetting(string key, string value)
{
_config[key] = value;
return this;
}

也就是说,接下来我们只要找到_config是如何注册到全局的ConfigurationBuilder中,就能拨开最后的迷雾,找到真正的答案。我们通过入口方法ConfigureWebHostDefaults往下找,虽然过程有点曲折,但是我们还是在GenericWebHostBuilder的构造函数中找到了如下逻辑逻辑[点击查看源码]

public GenericWebHostBuilder(IHostBuilder builder)
{
_builder = builder;
//这个就是上面UseSetting操作的_config
_config = new ConfigurationBuilder()
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
.Build();
//把_config通过ConfigureHostConfiguration方法注册到了全局的ConfigurationBuilder中去
_builder.ConfigureHostConfiguration(config =>
{
config.AddConfiguration(_config);
ExecuteHostingStartups();
});
//*其他部分代码省略
}

看到这个逻辑突然就恍然大悟了,我们上面曾经说过通过ConfigureHostConfiguration添加的配置相关,优先级是最低的。因为在HostBuilder.Build()调用的BuildAppConfiguration方法中我们可以得知,在循环执行_configureAppConfigActions循环之前,也就是在构建ConfigurationBuilder的时候就添加了ConfigureHostConfiguration。而UseSetting操作的Configuration正是通过ConfigureHostConfiguration注册到ConfigurationBuilder中去的,因此通过UseSetting添加的配置相关优先级要低于之前我们提到的其他配置逻辑。

总结

    通过本次谈到我们得到了默认情况下读取配置Configuration的默认优先级,也就是ConfigureAppConfiguration(自定义读取)>CommandLine(命令行参数)>Environment(环境变量)>appsetting.json(默认配置文件)>UseSetting的顺序。然后我们通过分析源码的形式,得到了为什么会是这个读取优先级的缘由。总之还是脱离不了那个宗旨,Configuration读取数据的顺序采用的是后来者居上的形式,后被注册的会优先被读取到。

    说点题外话,我觉得阅读源码是一件非常有趣的事情,不是说我要把所有源码看一遍,或者都能看懂。而是当我心理产生了疑惑,但是这个疑惑我通过阅读源码的途径变得豁然开朗,这才是读源码真正的乐趣所在。漫无目的或者为了读而读,会失去兴趣所在,容易导致效率低下,看明白了源码的设计,提升了自己的思维方式,也许才是真正的自我提升。

欢迎扫码关注我的公众号

深入探究.Net Core Configuration读取配置的优先级的更多相关文章

  1. ASP.NET Core实现强类型Configuration读取配置数据

    前言 实现读取JSON文件几种方式,在项目中采取老办法简单粗暴,结果老大过来一看,恩,这样不太可取,行吧那我就用.NET Core中最新的方式诺,切记,适合的才是最好的,切勿懒. .NET Core读 ...

  2. 使用Apache Commons Configuration读取配置信息

    在项目中使用一些比较新的库总会给你带来很多快乐,在这篇文章中,我将会给你介绍一个在Java中读取配置文件的框架——Apache Commons Configuration framework. 你会了 ...

  3. JavaWEB中读取配置信息

    第一种方法是使用java.io和java.util包,缺点是路径的概念要清晰, 例子: Properties prop = new Properties(); InputStream in = get ...

  4. 浅析Asp.Net Core框架IConfiguration配置

    目录 一.建造者模式(Builder Pattern) 二.核心接口与配置存储本质 三.简易QueryString配置源实现 四.宿主配置与应用配置 一.建造者模式 为什么提建造者模式?在阅读.NET ...

  5. .NET Core采用的全新配置系统[1]: 读取配置数据

    提到“配置”二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义在这两个文 ...

  6. ASP.NET Core的配置(1):读取配置信息

    提到"配置"二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化 ...

  7. NET Core开发-读取配置文件Configuration

    ASP.NET Core开发-读取配置文件Configuration   ASP.NET Core 是如何读取配置文件,今天我们来学习. ASP.NET Core的配置系统已经和之前版本的ASP.NE ...

  8. asp.net core 系列 10 配置configuration (上)

    一.  ASP.NET Core 中的配置概述 ASP.NET Core 中的应用配置是基于键值对,由configuration 程序提供. configuration  将从各种配置源提供程序操作键 ...

  9. 【ASP.NET Core快速入门】(五)命令行配置、Json文件配置、Bind读取配置到C#实例、在Core Mvc中使用Options

    命令行配置 我们通过vs2017创建一个控制台项目CommandLineSample 可以看到现在项目以来的是dotnet core framework 我们需要吧asp.net core引用进来,我 ...

随机推荐

  1. 2020牛客暑期多校训练营 第二场 B Boundary 计算几何 圆 已知三点求圆心

    LINK:Boundary 计算几何确实是弱项 因为好多东西都不太会求 没有到很精通的地步. 做法很多,先说官方题解 其实就是枚举一个点 P 然后可以发现 再枚举一个点 然后再判断有多少个点在圆上显然 ...

  2. 一本通 1783 矩阵填数 状压dp 容斥 计数

    LINK:矩阵填数 刚看到题目的时候感觉是无从下手的. 可以看到有n<=2的点 两个矩形. 如果只有一个矩形 矩形外的方案数容易计算考虑 矩形内的 必须要存在x这个最大值 且所有值<=x. ...

  3. 基于Python+Requests+Pytest+YAML+Allure实现接口自动化

    本项目实现接口自动化的技术选型:Python+Requests+Pytest+YAML+Allure ,主要是针对之前开发的一个接口项目来进行学习,通过 Python+Requests 来发送和处理H ...

  4. C++STL算法

    1.不变序列算法 不会修改算法所作用的容器或对象 适用于顺序容器和关联容器,时间复杂度为O(n). 2.变值算法 会修改源区间或目标区间元素的值,值被修改的那个区间,不可属于关联容器. 3.删除算法 ...

  5. Linux恢复删除后数据文件

    简介 在使用Linux系统时,有时候会不小心误删除数据,由于Linux系统也没有与Windows系统下回收站类似的功能,一般会认为该文件将无法找回. 本文主要以CentOS7操作系统为例,介绍如何使用 ...

  6. ios 淘宝评论详情、朋友圈布局masony实现

    最近做项目,用到了类似于淘宝的朋友圈的功能,然后自己抽出了一个小demo,与大家分享 介绍:用的是masony布局的cell这样的话,文本,以及图片可以自适应,不用人工再去计算高度,很方便. 注:该d ...

  7. JAVA程序设计环境

    JDK ,Java Development Kit(Java开发工具包) JRE ,Java Runtime Environment(Java运行时环境) SE   ,Standard Edition ...

  8. Linux 安装 PostgreSQL

    Linux 安装 PostgreSQL CentOS 7 安装 PostgreSQL 10 步骤 官网安装步骤,选择服务器和数据库版本,会给出相应的安装命令 # 安装 yum install -y h ...

  9. 关于手机数码圈KOL的一两点感想

    复工以来,高峰时段9号线地铁上的人依旧不少,安全距离啥的肯定是不用想了,只是从原来的4G手机换成5G手机以后在某些站能接收到5G信号,我终于能在一些原来根本没信号的站里愉快的刷一刷微博和酷安了. 但是 ...

  10. C#LeetCode刷题之#350-两个数组的交集 II(Intersection of Two Arrays II)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4044 访问. 给定两个数组,编写一个函数来计算它们的交集. 输入 ...