深入探究.Net Core Configuration读取配置的优先级

前言

    在之前的文章.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的顺序,采用的是后来者居上的方式//后注册的会先被读取到,如果读取到直接returnfor (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去构建一个全局的ConfigurationBuildervar 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读取数据的顺序采用的是后来者居上的形式,后被注册的会优先被读取到。
    说点题外话,我觉得阅读源码是一件非常有趣的事情,不是说我要把所有源码看一遍,或者都能看懂。而是当我心理产生了疑惑,但是这个疑惑我通过阅读源码的途径变得豁然开朗,这才是读源码真正的乐趣所在。漫无目的或者为了读而读,会失去兴趣所在,容易导致效率低下,看明白了源码的设计,提升了自己的思维方式,也许才是真正的自我提升。

????欢迎扫码关注我的公众号????

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307986.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TypeScript,从0到入门带你进入类型的世界

从0到入门进入TS的世界一、什么是TypeScript&#xff1f;1、编程语言的类型2、TypeScript究竟是什么&#xff1f;二、为什么要学习TypeScript&#xff1f;1、程序更容易理解2、效率更高3、更少的错误4、非常好的包容性5、一点小缺点三、typescript入门1、如何安装TypeScript2、…

编写第一个 .NET 微服务

介绍本文的目的是&#xff1a;通过创建一个返回列表的简单服务&#xff0c;并在 Docker 容器中运行该服务&#xff0c;让您熟悉使用 .NET 创建微服务的构建过程。安装 .NET SDK要开始构建 .NET 应用程序&#xff0c;首先下载并安装 .NET Core SDK&#xff08;软件开发工具包&am…

模板编译template的背后,究竟发生了什么事?带你了解template的纸短情长

解析模板编译template的背后发生了什么一、&#x1f4d1;初识模板编译1、vue组件中使用render代替template2、模板编译总结二、✏️感受模板编译的美1、with语法&#xff08;1&#xff09;例子展示&#x1f330;&#xff08;2&#xff09;知识点归纳三、&#x1f4c8;编译模板1…

leetcode24. 两两交换链表中的节点(思路+解析)

一:题目 二:思路 思路: 1.分析题意 这是相邻结点进行交换 如果是4个结点 那么1和2交换 3和4交换 如果是3个结点 那么就1和2进行交换 3不动 2.这里我们定义一个虚拟头节点方便操作&#xff0c;我们只需三步实现结点的交换 <1>:让虚拟结点指向第二个结点(进行交换的结点我…

把Autofac玩的和java Spring一样6

大家好&#xff0c;今天来介绍我开源的一个autofac.Annotation项目 源码&#xff1a;https://github.com/yuzd/Autofac.Annotation本项目是autofa的一个扩展组件&#xff0c;autofac是一个老牌的DI容器框架 &#xff0c;支持netframework和netcoreAnnotdation是注解的意思&…

『软件测试5』测开岗只要求会黑白盒测试?NO!还要学会性能测试!

浅谈软件测试中的性能测试一、&#x1f92a;性能测试概念1、为什么要有性能测试&#xff1f;2、性能测试是什么&#xff1f;3、性能测试的目的二、&#x1f910;性能测试指标1、响应时间2、吞吐量3、并发用户数4、TPS(Transaction Per Second)5、点击率6、资源利用率三、&#…

CLR的简单理解

CLR加载程序生成进程&#xff0c;一个进程中可以存在多个线程&#xff0c;当创建一个线程时&#xff0c;会分配1Mb的空间&#xff0c;也就是线程的栈空间&#xff0c;对应jvm的虚拟机堆栈&#xff0c;是线程执行过程中用到的工作内存。这片内存用于方法传递实参&#xff0c;并存…

『软件测试6』bug一两是小事,但安全漏洞是大事!

详解软件测试中的安全测试一、&#x1f4bf;安全测试概念1、安全测试概述2、安全测试与软件生命周期的关系3、常规测试与安全测试的不同&#xff08;1&#xff09;测试目标不同&#xff08;2&#xff09;假设条件不同&#xff08;3&#xff09;思考域不同&#xff08;4&#xf…

我们真的需要JWT吗?

JWT&#xff08;JSON Web Token&#xff09;是目前最流行的认证方案之一。博客园、各种技术公众号隔三差五就会推一篇JWT相关的文章&#xff0c;真的多如牛毛。但我对JWT有点困惑&#xff0c;今天写出来跟大家探讨探讨&#xff0c;不要喷哈。JWT原理本文默认读者已经对JWT有所了…

leetcode面试题 02.07. 链表相交

一:题目 二:思路 1.这道题我们是需要找到一个结点&#xff0c;并且从这个结点往后的结点都相等 2.我们需要将两个链表 右对齐 3.然后将长链表的指针移动到和短链表头结点相同的位置 4.接下来就是比较指针&#xff0c;当一个指针相同也就意味着往后的结点的数值也相等 三:上码…

详解队列在前端的应用,深剖JS中的事件循环Eventloop,再了解微任务和宏任务

队列在前端中的应用一、队列是什么二、应用场景三、前端与队列&#xff1a;事件循环与任务队列1、event loop2、JS如何执行3、event loop过程4、 DOM 事件和 event loop5、event loop 总结四、宏任务和微任务1、引例2、宏任务和微任务&#xff08;1&#xff09;常用的宏任务和微…

终于弄明白了 Singleton,Transient,Scoped 的作用域是如何实现的

一&#xff1a;背景1. 讲故事前几天有位朋友让我有时间分析一下 aspnetcore 中为什么向 ServiceCollection 中注入的 Class 可以做到 Singleton&#xff0c;Transient&#xff0c;Scoped&#xff0c;挺有意思&#xff0c;这篇就来聊一聊这一话题&#xff0c;自从 core 中有了 S…

leetcode142. 环形链表 II(暴力+双链表)

一:题目 二:思路 1.双指针 快慢指针(快指针一次一个结点&#xff0c;慢指针一次两个结点) 2.如果有环的话&#xff0c;那么快慢指针肯定会相遇 3.那么相遇的地点一定在环中 因为如果没有环的话慢指针是永远追不到快指针的 4.接下来就是判断出口在那里&#xff0c;我们定义一个…

动态 Restful API 生成

介绍通常在DDD开发架构中&#xff0c;我们写完服务层需要在控制器中写API&#xff0c;今天介绍一个组件 Plus.AutoApi 可以用它来动态生成 Restful 风格的 WebApi&#xff0c;不用写 Controller。快速使用在你的应用服务层中添加组件Install-Package Plus.AutoApi在 Startup 中…

卷死了!再不学vue3就没有人要你了!速来围观vue3新特性

一文全面了解vue3新特性一、&#x1f636;vue3比vue2有什么优势&#xff1f;二、&#x1f9d0;Vue3升级了哪些重要的功能1、createApp2、emits(父子组件间的通信)&#xff08;1&#xff09;通信方式&#xff08;2&#xff09;举个例子&#x1f330;3、多事件处理4、Fragment5、…

idea报错Class not found (在target中没有生成对应的class文件)

一&#xff1a;问题描述 二:解决 既然他不自动生成&#xff0c;那么我们就手动导入&#xff1b; 点击后应用 然后再次运行我们的测试用例&#xff1b;如果不行 再取消勾选 然后再运行我们的测试用例

敲黑板!vue3重点!一文了解Composition API新特性:ref、toRef、toRefs

一文了解Composition API新特性&#xff1a;ref、toRef、toRefs一、&#x1f64e;如何理解ref、toRef和toRefs1、ref、toRef和toRefs是什么&#xff08;1&#xff09;ref1&#xff09;ref是什么2&#xff09;举个例子&#x1f330;&#xff08;2&#xff09;toRef是什么1&#…

C# 枚举转列表

C# 枚举转列表独立观察员 2020 年 9 月 1 日今天有朋友问我&#xff0c;ComboBox 怎么绑定一个 Enum&#xff0c;其实他的意思是枚举如何转换为列表。想想这确实是一个挺正常的需求&#xff0c;但我一时也只想到遍历&#xff0c;他觉得麻烦&#xff0c;于是我在网上帮忙查了一下…

leetcode242. 有效的字母异位词(两种方法map或数组)

一:题目 二:上码 1:方法一&#xff08;map解法&#xff09; class Solution { public:bool isAnagram(string s, string t) {/**思路:1.分析题意&#xff0c;这个是要判断t中的字符出现次数和s中字符出现的次数相同2.可以用map<char,int>来做*/map<char,int>m1,m…

活久见!月薪30k的小程序全栈开发到底有多难?

10年前&#xff0c;公司的标配是门户网站&#xff0c;造就了一批网站工作室。随着移动互联网大潮兴起&#xff0c;App又成了企业标配&#xff0c;IOS和Android开发赚的盆满钵满。然而App导致的手机内存告急&#xff0c;无止尽的信息推送&#xff0c;让微信小程序应运而生。然而…