怎么在asp.net core中配置文件加载
创始人
2024-11-27 16:22:26
0次

何时加载了默认的配置文件

在 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();
 }

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(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>(
      new ConfigurationChangeTokenSource(hostingContext.Configuration));

     services.AddTransient();
    })
    .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(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>(
      new ConfigurationChangeTokenSource(hostingContext.Configuration));

     services.AddTransient();
    })
    .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(_hostingEnvironment);
 services.AddSingleton(_hostingEnvironment);
 services.AddSingleton(_context);

 var builder = new ConfigurationBuilder()
    .SetBasePath(_hostingEnvironment.ContentRootPath)
    .AddConfiguration(_config);

 _configureAppConfigurationBuilder?.Invoke(_context, builder);

 var configuration = builder.Build();
 services.AddSingleton(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();
   foreach (var source in Sources)
   {
    var provider = source.Build(this);
    providers.Add(provider);
   }
   return new ConfigurationRoot(providers);
  }

在我们执行 .Build 方法的时候,方法内部最后一行代码给我们利用 AddJsonFile 方法的参数创建并返回了一个 ConfigurationRoot 对象

在 ConfigurationRoot 的构造方法中

  public ConfigurationRoot(IList 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

  • 让人欣慰的是,我们可以自定义该值,如果业务对文件变动需求不是特别迫切,您可以将该值设置为一个很大的时间,通常情况下,我们不建议那么做

ASP.NET 是什么

ASP.NET 是开源,跨平台,高性能,轻量级的 Web 应用构建框架,常用于通过 HTML、CSS、JavaScript 以及服务器脚本来构建网页和网站。

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...