深入探究.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讀取數據的順序采用的是后來者居上的形式,后被注冊的會優先被讀取到。
????說點題外話,我覺得閱讀源碼是一件非常有趣的事情,不是說我要把所有源碼看一遍,或者都能看懂。而是當我心理產生了疑惑,但是這個疑惑我通過閱讀源碼的途徑變得豁然開朗,這才是讀源碼真正的樂趣所在。漫無目的或者為了讀而讀,會失去興趣所在,容易導致效率低下,看明白了源碼的設計,提升了自己的思維方式,也許才是真正的自我提升。
????歡迎掃碼關注我的公眾號????
總結
以上是生活随笔為你收集整理的深入探究.Net Core Configuration读取配置的优先级的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: Blazor带我重玩前端(六)
 - 下一篇: 编写第一个 .NET 微服务