前言配置文件中程序运行中,担当着不可或缺的角色;通常情况下,使用 visual studio 进行创建项目过程中,项目配置文件会自动生成在项目根目录下,如 appsettings。json,或者是被大家广泛使用的 appsettings。{env。
EnvironmentName}。json;配置文件作为一个入口,可以让我们在不更新代码的情况,对程序进行干预和调整,那么对其加载过程的全面了解就显得非常必要。何时加载了默认的配置文件在 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 内,当程序执行 WebHost。CreateDefaultBuilder(args) 的时候,在 CreateDefaultBuilder 方法内部加载了默认的配置文件代码如下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 内部还是使用了 IConfigurationBuilder 的实现,且写死了默认配置文件的名字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; }由于以上代码,我们可以在应用程序根目录下使用 appsettings。
json 和 appsettings。{env。EnvironmentName}。json 这种形式的默认配置文件名称并且,由于 Main 方法默认对配置文件进行了 Build 方法的调用操作public static void Main(string[] args) { CreateWebHostBuilder(args)。
Build()。Run(); }我们可以在 Startup。cs 中使用注入的方式获得默认的配置文件对象 IConfigurationRoot/IConfiguration,代码片段public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; }这是为什么呢,因为在 执行 Build 方法的时候,方法内部已经将默认配置文件对象加入了 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;以上这段代码非常熟悉,因为在 Startup。
cs 文件中,我们也许会使用过 ServiceCollection 对象将业务系统的自定义对象加入服务上下文中,以方便后续接口注入使用。AddJsonFile 方法的使用通常情况下,我们都会使用默认的配置文件进行开发,或者使用 appsettings。
{env。EnvironmentName}。json 的文件名称方式来区分 开发/测试/产品 环境,根据环境变量加载不同的配置文件;可是这样一来带来了另外一个管理上的问题,产品环境的配置参数和开发环境是不同的,如果使用环境变量的方式控制配置文件的加载,则可能导致密码泄露等风险;诚然,可以手工在产品环境创建此文件,但是这样一来,发布流程将会变得非常繁琐,稍有错漏文件便会被覆盖。
我们推荐使用 AddJsonFile 加载产品环境配置,代码如下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; }通过 AddCustomizedJsonFile 方法去创建一个 ConfigurationBuilder 对象,并覆盖系统默认的 ConfigurationBuilder 对象,在方法内部,默认加载开发环境的配置文件,在产品模式下,额外加载目录 /data/sites/config/appsettings。
json 文件。不同担心配置文件冲突问题,相同键值的内容将由后加入的配置文件所覆盖。配置文件的变动在调用 AddJsonFile 时,我们看到该方法共有 5 个重载的方法其中一个方法包含了 4 个参数,代码如下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(); }); }在此方法中,有一个参数 bool reloadOnChange,从参数描述可知,该值指示在文件变动的时候是否重新加载,默认值为:false;一般在手动加载配置文件,即调用 AddJsonFile 方法时,建议将该参数值设置为 true。
那么 。netcore 是如果通过该参数 reloadOnChange 是来监控文件变动,以及何时进行重新加载的操作呢,看下面代码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); }在我们执行 。Build 方法的时候,方法内部最后一行代码给我们利用 AddJsonFile 方法的参数创建并返回了一个 ConfigurationRoot 对象在 ConfigurationRoot 的构造方法中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()); } }我们看到,方法内部一次读取了通过 AddJsonFile 方法加入的配置文件,并为每个配置文件单独分配了一个监听器 ChangeToken,并绑定当前文件读取对象 IConfigurationProvider。
GetReloadToken 方法到监听器中当文件产生变动的时候,监听器会收到一个通知,同时,对该文件执行原子操作private void RaiseChanged() { var previousToken = Interlocked。Exchange(ref _changeToken, new ConfigurationReloadToken()); previousToken。
OnReload(); }由于 AddJsonFile 方法内部使用了 JsonConfigurationSource ,而 Build 的重载方法构造了一个 JsonConfigurationProvider 读取对象,查看代码public override IConfigurationProvider Build(IConfigurationBuilder builder) { EnsureDefaults(builder); return new JsonConfigurationProvider(this); }在 JsonConfigurationProvider 继承自 FileConfigurationProvider 类,该类位于程序集 Microsoft。
Extensions。Configuration。Json。dll 内在 FileConfigurationProvider 的构造方法中实现了监听器重新加载配置文件的过程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); }); } }值得注意的是,该监听器不是在得到文件变动通知后第一时间去重新加载配置文件,方法内部可以看到,这里有一个 Thread。Sleep(Source。
ReloadDelay) ,而 ReloadDelay 的默认值为:250ms,该属性的描述为获取或者设置重新加载将等待的毫秒数, 然后调用 “Load” 方法。 这有助于避免在完全写入文件之前触发重新加载。默认值为250 让人欣慰的是,我们可以自定义该值,如果业务对文件变动需求不是特别迫切,您可以将该值设置为一个很大的时间,通常情况下,我们不建议那么做结语。

相关推荐