前言

    在之前的文章.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. PHP xml_set_object() 函数

    定义和用法 xml_set_object() 函数允许在对象中使用 XML 解析器.高佣联盟 www.cgewang.com 如果成功,该函数则返回 TRUE.如果失败,则返回 FALSE. 语法 x ...

  2. PHP connection_status() 函数

    实例 返回连接状态: <?phpswitch (connection_status()){高佣联盟 www.cgewang.comcase CONNECTION_NORMAL:$txt = 'C ...

  3. 7.1 NOI模拟赛 计数问题 dp

    还是可以想出来的题目 不过考场上没有想出来 要 引以为戒. 初看觉得有点不可做 10分给到了爆搜. 考虑第一个特殊情况 B排列为1~m. 容易发现A排列中前m个数字 他们之间不能产生交换 且 第k个数 ...

  4. 5.29 省选模拟赛 树的染色 dp 最优性优化

    LINK:树的染色 考场上以为这道题要爆蛋了 没想到 推出正解来了. 反正是先写了爆搜的 爆搜最近越写越熟练了 容易想到dp 容易设出状态 f[i][j]表示以i为根的子树内白色的值为j此时黑色的值怎 ...

  5. Android JNI之编译

    JNI代码都写好了,在编译之前我们有非常重要的一部,就是写mk文件,mk文件就相当于gcc编译时的Makefile文件,它是用来告诉编译器如何去编译的. 这里只对自己理解和常用的知识点做记录,想要看关 ...

  6. Spring Cloud及微服务简介

    最近在看微服务编排的东西,看到一篇入门博客,私以为不错,再次分享下:https://blog.csdn.net/w05980598/article/details/79007194 什么是微服务 微服 ...

  7. Javascript 创建对象的三种方式

    function createPerson(name, qq) //工厂方式 { //在工厂里创建个对象 var obj=new Object(); obj.name=name; obj.qq=qq; ...

  8. MySQL进阶篇(03):合理的使用索引结构和查询

    本文源码:GitHub·点这里 || GitEE·点这里 一.高性能索引 1.查询性能问题 在MySQL使用的过程中,所谓的性能问题,在大部分的场景下都是指查询的性能,导致查询缓慢的根本原因是数据量的 ...

  9. Hexo小技巧(包括如何插入本地图片)

    我在研究如何在Hexo中引用本地图片时,看到官方文档对此问题已给出了解决方法,并亲测有效.当然,我并不满足于仅仅知道这一个技巧.在大致阅读过官方文档后,我总结了之前我个人并不知道的几个关于Hexo写博 ...

  10. 面试这么撩准拿offer,HashMap深度学习,扰动函数、负载因子、扩容拆分,原理和实践验证,让懂了就是真的懂!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 得益于Doug Lea老爷子的操刀,让HashMap成为使用和面试最频繁的API,没 ...