asp.net core配置文件加載過(guò)程的深入了解
前言
配置文件中程序運(yùn)行中,擔(dān)當(dāng)著不可或缺的角色;通常情況下,使用 visual studio 進(jìn)行創(chuàng)建項(xiàng)目過(guò)程中,項(xiàng)目配置文件會(huì)自動(dòng)生成在項(xiàng)目根目錄下,如 appsettings.json,或者是被大家廣泛使用的 appsettings.{env.EnvironmentName}.json;
配置文件
作為一個(gè)入口,可以讓我們?cè)诓桓麓a的情況,對(duì)程序進(jìn)行干預(yù)和調(diào)整,那么對(duì)其加載過(guò)程的全面了解就顯得非常必要。
何時(shí)加載了默認(rèn)的配置文件
在 Program.cs 文件中,查看以下代碼
public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); }
WebHost.CreateDefaultBuilder
位于程序集 Microsoft.AspNetCore.dll
內(nèi),當(dāng)程序執(zhí)行 WebHost.CreateDefaultBuilder(args)
的時(shí)候,在 CreateDefaultBuilder 方法內(nèi)部加載了默認(rèn)的配置文件
代碼如下
public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder(); if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey))) { builder.UseContentRoot(Directory.GetCurrentDirectory()); } if (args != null) { builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build()); } builder.UseKestrel((builderContext, options) => { options.Configure(builderContext.Configuration.GetSection("Kestrel")); }) .ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment()) { var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); if (appAssembly != null) { config.AddUserSecrets(appAssembly, optional: true); } } config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); logging.AddEventSourceLogger(); }) .ConfigureServices((hostingContext, services) => { // Fallback services.PostConfigure<HostFilteringOptions>(options => { if (options.AllowedHosts == null || options.AllowedHosts.Count == 0) { // "AllowedHosts": "localhost;127.0.0.1;[::1]" var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); // Fall back to "*" to disable. options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" }); } }); // Change notification services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>( new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration)); services.AddTransient<IStartupFilter, HostFilteringStartupFilter>(); }) .UseIIS() .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }); return builder; }
可以看到,CreateDefaultBuilder 內(nèi)部還是使用了 IConfigurationBuilder 的實(shí)現(xiàn),且寫死了默認(rèn)配置文件的名字
public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder(); if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey))) { builder.UseContentRoot(Directory.GetCurrentDirectory()); } if (args != null) { builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build()); } builder.UseKestrel((builderContext, options) => { options.Configure(builderContext.Configuration.GetSection("Kestrel")); }) .ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment()) { var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); if (appAssembly != null) { config.AddUserSecrets(appAssembly, optional: true); } } config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); logging.AddEventSourceLogger(); }) .ConfigureServices((hostingContext, services) => { // Fallback services.PostConfigure<HostFilteringOptions>(options => { if (options.AllowedHosts == null || options.AllowedHosts.Count == 0) { // "AllowedHosts": "localhost;127.0.0.1;[::1]" var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); // Fall back to "*" to disable. options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" }); } }); // Change notification services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>( new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration)); services.AddTransient<IStartupFilter, HostFilteringStartupFilter>(); }) .UseIIS() .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }); return builder; }
由于以上代碼,我們可以在應(yīng)用程序根目錄下使用 appsettings.json
和 appsettings.{env.EnvironmentName}.json
這種形式的默認(rèn)配置文件名稱
并且,由于 Main 方法默認(rèn)對(duì)配置文件進(jìn)行了 Build 方法的調(diào)用操作
public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); }
我們可以在 Startup.cs 中使用注入的方式獲得默認(rèn)的配置文件對(duì)象 IConfigurationRoot/IConfiguration,代碼片段
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; }
這是為什么呢,因?yàn)樵?執(zhí)行 Build 方法的時(shí)候,方法內(nèi)部已經(jīng)將默認(rèn)配置文件對(duì)象加入了 ServiceCollection 中,代碼片段
var services = new ServiceCollection(); services.AddSingleton(_options); services.AddSingleton<IHostingEnvironment>(_hostingEnvironment); services.AddSingleton<Extensions.Hosting.IHostingEnvironment>(_hostingEnvironment); services.AddSingleton(_context); var builder = new ConfigurationBuilder() .SetBasePath(_hostingEnvironment.ContentRootPath) .AddConfiguration(_config); _configureAppConfigurationBuilder?.Invoke(_context, builder); var configuration = builder.Build(); services.AddSingleton<IConfiguration>(configuration); _context.Configuration = configuration;
以上這段代碼非常熟悉,因?yàn)樵?Startup.cs 文件中,我們也許會(huì)使用過(guò) ServiceCollection 對(duì)象將業(yè)務(wù)系統(tǒng)的自定義對(duì)象加入服務(wù)上下文中,以方便后續(xù)接口注入使用。
AddJsonFile 方法的使用
通常情況下,我們都會(huì)使用默認(rèn)的配置文件進(jìn)行開(kāi)發(fā),或者使用 appsettings.{env.EnvironmentName}.json 的文件名稱方式來(lái)區(qū)分 開(kāi)發(fā)/測(cè)試/產(chǎn)品 環(huán)境,根據(jù)環(huán)境變量加載不同的配置文件;可是這樣一來(lái)帶來(lái)了另外一個(gè)管理上的問(wèn)題,產(chǎn)品環(huán)境的配置參數(shù)和開(kāi)發(fā)環(huán)境
是不同的,如果使用環(huán)境變量的方式控制配置文件的加載,則可能導(dǎo)致密碼泄露等風(fēng)險(xiǎn);誠(chéng)然,可以手工在產(chǎn)品環(huán)境創(chuàng)建此文件,但是這樣一來(lái),發(fā)布流程將會(huì)變得非常繁瑣,稍有錯(cuò)漏文件便會(huì)被覆蓋。
我們推薦使用 AddJsonFile 加載產(chǎn)品環(huán)境配置,代碼如下
public Startup(IConfiguration configuration, IHostingEnvironment env) { Configuration = AddCustomizedJsonFile(env).Build(); } public ConfigurationBuilder AddCustomizedJsonFile(IHostingEnvironment env) { var build = new ConfigurationBuilder(); build.SetBasePath(env.ContentRootPath).AddJsonFile("appsettings.json", true, true); if (env.IsProduction()) { build.AddJsonFile(Path.Combine("/data/sites/config", "appsettings.json"), true, true); } return build; }
通過(guò) AddCustomizedJsonFile 方法去創(chuàng)建一個(gè) ConfigurationBuilder 對(duì)象,并覆蓋系統(tǒng)默認(rèn)的 ConfigurationBuilder 對(duì)象,在方法內(nèi)部,默認(rèn)加載開(kāi)發(fā)環(huán)境的配置文件,在產(chǎn)品模式下,額外加載目錄 /data/sites/config/appsettings.json 文件,
不同擔(dān)心配置文件沖突問(wèn)題,相同鍵值的內(nèi)容將由后加入的配置文件所覆蓋。
配置文件的變動(dòng)
在調(diào)用 AddJsonFile 時(shí),我們看到該方法共有 5 個(gè)重載的方法
其中一個(gè)方法包含了 4 個(gè)參數(shù),代碼如下
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (string.IsNullOrEmpty(path)) { throw new ArgumentException(Resources.Error_InvalidFilePath, nameof(path)); } return builder.AddJsonFile(s => { s.FileProvider = provider; s.Path = path; s.Optional = optional; s.ReloadOnChange = reloadOnChange; s.ResolveFileProvider(); }); }
在此方法中,有一個(gè)參數(shù) bool reloadOnChange,從參數(shù)描述可知,該值指示在文件變動(dòng)的時(shí)候是否重新加載,默認(rèn)值為:false;一般在手動(dòng)加載配置文件,即調(diào)用 AddJsonFile 方法時(shí),建議將該參數(shù)值設(shè)置為 true。
那么 .netcore 是如果通過(guò)該參數(shù) reloadOnChange 是來(lái)監(jiān)控文件變動(dòng),以及何時(shí)進(jìn)行重新加載的操作呢,看下面代碼
public IConfigurationRoot Build() { var providers = new List<IConfigurationProvider>(); foreach (var source in Sources) { var provider = source.Build(this); providers.Add(provider); } return new ConfigurationRoot(providers); }
在我們執(zhí)行 .Build 方法的時(shí)候,方法內(nèi)部最后一行代碼給我們利用 AddJsonFile 方法的參數(shù)創(chuàng)建并返回了一個(gè) ConfigurationRoot 對(duì)象
在 ConfigurationRoot 的構(gòu)造方法中
public ConfigurationRoot(IList<IConfigurationProvider> providers) { if (providers == null) { throw new ArgumentNullException(nameof(providers)); } _providers = providers; foreach (var p in providers) { p.Load(); ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()); } }
我們看到,方法內(nèi)部一次讀取了通過(guò) AddJsonFile 方法加入的配置文件,并為每個(gè)配置文件單獨(dú)分配了一個(gè)監(jiān)聽(tīng)器 ChangeToken,并綁定當(dāng)前文件讀取對(duì)象 IConfigurationProvider.GetReloadToken
方法到監(jiān)聽(tīng)器中
當(dāng)文件產(chǎn)生變動(dòng)的時(shí)候,監(jiān)聽(tīng)器會(huì)收到一個(gè)通知,同時(shí),對(duì)該文件執(zhí)行原子操作
private void RaiseChanged() { var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); previousToken.OnReload(); }
由于 AddJsonFile 方法內(nèi)部使用了 JsonConfigurationSource ,而 Build 的重載方法構(gòu)造了一個(gè) JsonConfigurationProvider 讀取對(duì)象,查看代碼
public override IConfigurationProvider Build(IConfigurationBuilder builder) { EnsureDefaults(builder); return new JsonConfigurationProvider(this); }
在 JsonConfigurationProvider 繼承自 FileConfigurationProvider 類,該類位于程序集 Microsoft.Extensions.Configuration.Json.dll
內(nèi)
在 FileConfigurationProvider 的構(gòu)造方法中實(shí)現(xiàn)了監(jiān)聽(tīng)器重新加載配置文件的過(guò)程
public FileConfigurationProvider(FileConfigurationSource source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } Source = source; if (Source.ReloadOnChange && Source.FileProvider != null) { ChangeToken.OnChange( () => Source.FileProvider.Watch(Source.Path), () => { Thread.Sleep(Source.ReloadDelay); Load(reload: true); }); } }
值得注意的是,該監(jiān)聽(tīng)器不是在得到文件變動(dòng)通知后第一時(shí)間去重新加載配置文件,方法內(nèi)部可以看到,這里有一個(gè) Thread.Sleep(Source.ReloadDelay)
,而 ReloadDelay 的默認(rèn)值為:250ms,該屬性的描述為
- 獲取或者設(shè)置重新加載將等待的毫秒數(shù), 然后調(diào)用 "Load" 方法。 這有助于避免在完全寫入文件之前觸發(fā)重新加載。默認(rèn)值為250
- 讓人欣慰的是,我們可以自定義該值,如果業(yè)務(wù)對(duì)文件變動(dòng)需求不是特別迫切,您可以將該值設(shè)置為一個(gè)很大的時(shí)間,通常情況下,我們不建議那么做
結(jié)語(yǔ)
以上就是 asp.netcore 中配置文件加載的內(nèi)部執(zhí)行過(guò)程,從中我們認(rèn)識(shí)到,默認(rèn)配置文件是如何加載,并將默認(rèn)配置文件如何注入到系統(tǒng)中的,還學(xué)習(xí)到了如果在不同的環(huán)境下,選擇加載自定義配置文件的過(guò)程;但配置文件變動(dòng)的時(shí)候,系統(tǒng)內(nèi)部又是如何去把配置文件重新加載到內(nèi)存中去的。
相關(guān)文章
.NET Core系列之MemoryCache 緩存過(guò)期
這篇文章主要介紹了.NET Core系列之MemoryCache 緩存過(guò)期,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08ASP.NET數(shù)據(jù)庫(kù)存取圖片的方法
這篇文章主要為大家詳細(xì)介紹了ASP.NET數(shù)據(jù)庫(kù)如何存取圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01已有打開(kāi)的與此命令相關(guān)聯(lián)的DataReader,必須首先將它關(guān)閉。對(duì)于此異常的理解
今天與大家分享一下 已有打開(kāi)的與此命令相關(guān)聯(lián)的DataReader,必須首先將它關(guān)閉。這個(gè)異常的個(gè)人理解2012-01-01ASP.NET?MVC使用Knockout獲取數(shù)組元素索引的2種方法
這篇文章介紹了ASP.NET?MVC使用Knockout獲取數(shù)組元素索引的2種方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08Entity?Framework生成DataBase?First模式
本文詳細(xì)講解了Entity?Framework生成DataBase?First模式的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03DataGridView使用BindingNavigator實(shí)現(xiàn)簡(jiǎn)單分頁(yè)功能
這篇文章主要介紹了DataGridView使用BindingNavigator實(shí)現(xiàn)簡(jiǎn)單分頁(yè)功能,本文主要是通過(guò)借用BindingNavigator空殼,文中通過(guò)實(shí)例代碼講解的非常詳細(xì),需要的朋友可以參考下2019-11-11詳解ASP.NET Core 中基于工廠的中間件激活的實(shí)現(xiàn)方法
這篇文章主要介紹了ASP.NET Core 中基于工廠的中間件激活的實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11創(chuàng)建一個(gè)完整的ASP.NET Web API項(xiàng)目
ASP.NET Web API具有與ASP.NET MVC類似的編程方式,ASP.NET Web API不僅僅具有一個(gè)完全獨(dú)立的消息處理管道,而且這個(gè)管道比為ASP.NET MVC設(shè)計(jì)的管道更為復(fù)雜,功能也更為強(qiáng)大。下面創(chuàng)建一個(gè)簡(jiǎn)單的Web API項(xiàng)目,需要的朋友可以參考下2015-10-10.NET使用Hisql實(shí)現(xiàn)菜單管理(增刪改查)
這篇文章介紹了.NET使用Hisql實(shí)現(xiàn)菜單管理(增刪改查)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07IIS服務(wù)器發(fā)布ASP.NET項(xiàng)目
如何在云服務(wù)器上部署一個(gè)項(xiàng)目,需要做哪些配置準(zhǔn)備,本文就來(lái)介紹一下IIS服務(wù)器發(fā)布ASP.NET項(xiàng)目,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03