ASP.NET Core中的配置Configuration的使用及其源码解析

  • A+
所属分类:.NET技术
摘要

本章将和大家分享ASP.NET Core中的配置Configuration的使用及其源码解析。Demo的目录结构如下所示:

本章将和大家分享ASP.NET Core中的配置Configuration的使用及其源码解析。

1、使用 IConfiguration 读取配置文件内容

Demo的目录结构如下所示:

ASP.NET Core中的配置Configuration的使用及其源码解析

本Demo的Web项目为ASP.NET Core Web 应用程序(目标框架为.NET Core 3.1) MVC项目。 

首先添加配置文件,内容如下所示:(注意:配置文件的编码必须调整为UTF-8

appsettings.Development.json 内容如下:

{   "Logging": {     "LogLevel": {       "Default": "Information",       "Microsoft": "Warning",       "Microsoft.Hosting.Lifetime": "Information"     }   } }

appsettings.json 内容如下:

{   "Logging": {     "LogLevel": {       "Default": "Information",       "Microsoft": "Warning",       "Microsoft.Hosting.Lifetime": "Information"     }   },   "AllowedHosts": "*" }

hostsettings.json 内容如下:

{   "urls": "http://*:8001" }

otherconfig.json 内容如下:

{   "OtherConfig": {     "accountkey": "accountkey_测试",     "accountsecret": "accountsecret_测试"   } }

otherconfig.Production.json 内容如下:

{   "OtherConfig": {     "accountkey": "accountkey_正式",     "accountsecret": "accountsecret_正式"   } }

siteconfig.json 内容如下:

{   "ConnectionStrings": "server=127.0.0.1;port=3306;database=dotnetcore;uid=root;pwd=123456;CharSet=utf8;",   "SiteConfig": {     "Name": "测试站点名称",     "Admin": "http://admin.demo.test",     "Api": "http://api.demo.test",     "Image": "http://image.demo.test",     "My": "http://my.demo.test",     "Upload": "http://upload.demo.test",     "Www": "http://www.demo.test",      "Domain": {       "mc_1633_com": "http://192.168.18.209:8000/mc"     }   } }

siteconfig.Production.json 内容如下:

{   "ConnectionStrings": "server=127.0.0.1;port=3306;database=dotnetcore;uid=root;pwd=123456;CharSet=utf8;",   "SiteConfig": {     "Name": "正式站点名称",     "Admin": "http://admin.demo.com",     "Api": "http://api.demo.com",     "Image": "http://image.demo.com",     "My": "http://my.demo.com",     "Upload": "http://upload.demo.com",     "Www": "http://www.demo.com"   } }

接着修改 Program.cs 类,如下所示: 

using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks;  namespace IConfiguration_IOptions_Demo {     public class Program     {         public static void Main(string[] args)         {             //CreateHostBuilder(args).Build().Run();             var hostBuilder = CreateHostBuilder(args);             hostBuilder.Build().Run();         }          public static IHostBuilder CreateHostBuilder(string[] args) =>             Host.CreateDefaultBuilder(args)                 .ConfigureAppConfiguration((hostingContext, config) =>                 {                     var env = hostingContext.HostingEnvironment;                     var basePath = System.IO.Path.Combine(env.ContentRootPath, "ConfigFiles");                      config.SetBasePath(basePath: basePath)                     .AddJsonFile(path: "hostsettings.json", optional: true, reloadOnChange: true)                     .AddJsonFile(path: $"hostsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)                      .AddJsonFile(path: "otherconfig.json", optional: true, reloadOnChange: true)                     .AddJsonFile(path: $"otherconfig.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)                      .AddJsonFile(path: "siteconfig.json", optional: true, reloadOnChange: true)                     .AddJsonFile(path: $"siteconfig.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);                      config.AddEnvironmentVariables();                 })                 .ConfigureWebHostDefaults(webBuilder =>                 {                     webBuilder.UseStartup<Startup>();                 });     } }

最后使用 IConfiguration 读取配置文件内容,如下所示:

using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;  namespace IConfiguration_IOptions_Demo.Controllers {     public class ConfigurationDemoController : Controller     {         private readonly IConfiguration _configuration;          /// <summary>         /// 构造函数         /// </summary>         public ConfigurationDemoController(IConfiguration configuration)         {             _configuration = configuration;         }          /// <summary>         /// IConfiguration的使用         /// </summary>         /// <returns></returns>         public IActionResult Index()         {             var sb = new StringBuilder();             sb.AppendLine($"_configuration["Logging:LogLevel:Default"] => {_configuration["Logging:LogLevel:Default"]}");             sb.AppendLine($"_configuration["AllowedHosts"] => {_configuration["AllowedHosts"]}");             sb.AppendLine($"_configuration["SiteConfig:Name"] => {_configuration["SiteConfig:Name"]}");             sb.AppendLine($"_configuration["OtherConfig:accountkey"] => {_configuration["OtherConfig:accountkey"]}");              IConfigurationSection siteConfigSection = _configuration.GetSection("SiteConfig");             sb.AppendLine($"siteConfigSection["Name"] => {siteConfigSection["Name"]}");             sb.AppendLine($"siteConfigSection["Domain:mc_1633_com"] => {siteConfigSection["Domain:mc_1633_com"]}");              IConfigurationSection domainSection = _configuration.GetSection("SiteConfig").GetSection("Domain");             sb.AppendLine($"domainSection["mc_1633_com"] => {domainSection["mc_1633_com"]}");              return Content(sb.ToString());         }     } }

访问 /ConfigurationDemo/Index 运行结果如下:

ASP.NET Core中的配置Configuration的使用及其源码解析

2、IConfiguration 源码解析

首先我们通过调试来看下注入给IConfiguration的到底是什么?

ASP.NET Core中的配置Configuration的使用及其源码解析

可以看出它是 Microsoft.Extensions.Configuration.ConfigurationRoot 类的实例。

我们接着往下调试看下 _configuration.GetSection("SiteConfig")  返回的是啥?

ASP.NET Core中的配置Configuration的使用及其源码解析

可以看出它返回的是 Microsoft.Extensions.Configuration.ConfigurationSection 类的实例。

其实 ConfigurationRoot 类实现了 IConfigurationRoot 接口,而 IConfigurationRoot 接口又实现了 IConfiguration 接口,并且 ConfigurationSection 类实现了 IConfigurationSection 接口,而 IConfigurationSection 接口又实现了 IConfiguration 接口。IConfiguration、IConfigurationRoot 和 IConfigurationSection 它们三者之间的关系如下:

ASP.NET Core中的配置Configuration的使用及其源码解析

讲到这里,大家对配置的获取,应该有了一个基本的认识。 那么下面我们就通过查看源码的方式来理解它的底层实现逻辑。

我们从 Program.cs 类的 Main(string[] args) 主函数(应用程序的入口)开始查找:

using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks;  namespace IConfiguration_IOptions_Demo {     public class Program     {         public static void Main(string[] args)         {             //CreateHostBuilder(args).Build().Run();             var hostBuilder = CreateHostBuilder(args);             hostBuilder.Build().Run();         }          public static IHostBuilder CreateHostBuilder(string[] args) =>             Host.CreateDefaultBuilder(args)                 .ConfigureAppConfiguration((hostingContext, config) =>                 {                     var env = hostingContext.HostingEnvironment;                     var basePath = System.IO.Path.Combine(env.ContentRootPath, "ConfigFiles");                      config.SetBasePath(basePath: basePath)                     .AddJsonFile(path: "hostsettings.json", optional: true, reloadOnChange: true)                     .AddJsonFile(path: $"hostsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)                      .AddJsonFile(path: "otherconfig.json", optional: true, reloadOnChange: true)                     .AddJsonFile(path: $"otherconfig.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)                      .AddJsonFile(path: "siteconfig.json", optional: true, reloadOnChange: true)                     .AddJsonFile(path: $"siteconfig.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);                      config.AddEnvironmentVariables();                 })                 .ConfigureWebHostDefaults(webBuilder =>                 {                     webBuilder.UseStartup<Startup>();                 });     } }

首先找到 Host 类,源码如下:

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System.IO; using System.Reflection; using System.Runtime.InteropServices; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.EventLog;  namespace Microsoft.Extensions.Hosting {     /// <summary>     /// Provides convenience methods for creating instances of <see cref="IHostBuilder"/> with pre-configured defaults.     /// </summary>     public static class Host     {         /// <summary>         /// Initializes a new instance of the <see cref="HostBuilder"/> class with pre-configured defaults.         /// </summary>         /// <remarks>         ///   The following defaults are applied to the returned <see cref="HostBuilder"/>:         ///   <list type="bullet">         ///     <item><description>set the <see cref="IHostEnvironment.ContentRootPath"/> to the result of <see cref="Directory.GetCurrentDirectory()"/></description></item>         ///     <item><description>load host <see cref="IConfiguration"/> from "DOTNET_" prefixed environment variables</description></item>         ///     <item><description>load app <see cref="IConfiguration"/> from 'appsettings.json' and 'appsettings.[<see cref="IHostEnvironment.EnvironmentName"/>].json'</description></item>         ///     <item><description>load app <see cref="IConfiguration"/> from User Secrets when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development' using the entry assembly</description></item>         ///     <item><description>load app <see cref="IConfiguration"/> from environment variables</description></item>         ///     <item><description>configure the <see cref="ILoggerFactory"/> to log to the console, debug, and event source output</description></item>         ///     <item><description>enables scope validation on the dependency injection container when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development'</description></item>         ///   </list>         /// </remarks>         /// <returns>The initialized <see cref="IHostBuilder"/>.</returns>         public static IHostBuilder CreateDefaultBuilder() =>             CreateDefaultBuilder(args: null);          /// <summary>         /// Initializes a new instance of the <see cref="HostBuilder"/> class with pre-configured defaults.         /// </summary>         /// <remarks>         ///   The following defaults are applied to the returned <see cref="HostBuilder"/>:         ///   <list type="bullet">         ///     <item><description>set the <see cref="IHostEnvironment.ContentRootPath"/> to the result of <see cref="Directory.GetCurrentDirectory()"/></description></item>         ///     <item><description>load host <see cref="IConfiguration"/> from "DOTNET_" prefixed environment variables</description></item>         ///     <item><description>load host <see cref="IConfiguration"/> from supplied command line args</description></item>         ///     <item><description>load app <see cref="IConfiguration"/> from 'appsettings.json' and 'appsettings.[<see cref="IHostEnvironment.EnvironmentName"/>].json'</description></item>         ///     <item><description>load app <see cref="IConfiguration"/> from User Secrets when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development' using the entry assembly</description></item>         ///     <item><description>load app <see cref="IConfiguration"/> from environment variables</description></item>         ///     <item><description>load app <see cref="IConfiguration"/> from supplied command line args</description></item>         ///     <item><description>configure the <see cref="ILoggerFactory"/> to log to the console, debug, and event source output</description></item>         ///     <item><description>enables scope validation on the dependency injection container when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development'</description></item>         ///   </list>         /// </remarks>         /// <param name="args">The command line args.</param>         /// <returns>The initialized <see cref="IHostBuilder"/>.</returns>         public static IHostBuilder CreateDefaultBuilder(string[] args)         {             var builder = new HostBuilder();              builder.UseContentRoot(Directory.GetCurrentDirectory());             builder.ConfigureHostConfiguration(config =>             {                 config.AddEnvironmentVariables(prefix: "DOTNET_");                 if (args != null)                 {                     config.AddCommandLine(args);                 }             });              builder.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() && !string.IsNullOrEmpty(env.ApplicationName))                 {                     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) =>             {                 var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);                  // IMPORTANT: This needs to be added *before* configuration is loaded, this lets                 // the defaults be overridden by the configuration.                 if (isWindows)                 {                     // Default the EventLogLoggerProvider to warning or above                     logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);                 }                  logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));                 logging.AddConsole();                 logging.AddDebug();                 logging.AddEventSourceLogger();                  if (isWindows)                 {                     // Add the EventLogLoggerProvider on windows machines                     logging.AddEventLog();                 }             })             .UseDefaultServiceProvider((context, options) =>             {                 var isDevelopment = context.HostingEnvironment.IsDevelopment();                 options.ValidateScopes = isDevelopment;                 options.ValidateOnBuild = isDevelopment;             });              return builder;         }     } }

从该类可以看出调用Host.CreateDefaultBuilder(args)这个方法最终返回的是HostBuilder类型的对象,且在Main(string[] args)主函数中最终会调用该对象的Build()方法。

此外还可以看出 Host.CreateDefaultBuilder(args) 这个方法的内部默认就已经添加了appsettings.json的这个配置,这也就解释了为什么在Program.cs类中添加配置文件的时候不需要加appsettings.json的原因。

接下来我们就来看一下 builder.ConfigureAppConfiguration(...)  这个方法内部到底做了什么操作,我们找到 HostBuilder 类的源码,如下:

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.Collections.Generic; using System.IO; using System.Reflection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting.Internal;  namespace Microsoft.Extensions.Hosting {     /// <summary>     /// A program initialization utility.     /// </summary>     public class HostBuilder : IHostBuilder     {         private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>();         private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>();         private List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>();         private List<IConfigureContainerAdapter> _configureContainerActions = new List<IConfigureContainerAdapter>();         private IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());         private bool _hostBuilt;         private IConfiguration _hostConfiguration;         private IConfiguration _appConfiguration;         private HostBuilderContext _hostBuilderContext;         private HostingEnvironment _hostingEnvironment;         private IServiceProvider _appServices;          /// <summary>         /// A central location for sharing state between components during the host building process.         /// </summary>         public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();          /// <summary>         /// Set up the configuration for the builder itself. This will be used to initialize the <see cref="IHostEnvironment"/>         /// for use later in the build process. This can be called multiple times and the results will be additive.         /// </summary>         /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used         /// to construct the <see cref="IConfiguration"/> for the host.</param>         /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>         public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)         {             _configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));             return this;         }          /// <summary>         /// Sets up the configuration for the remainder of the build process and application. This can be called multiple times and         /// the results will be additive. The results will be available at <see cref="HostBuilderContext.Configuration"/> for         /// subsequent operations, as well as in <see cref="IHost.Services"/>.         /// </summary>         /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used         /// to construct the <see cref="IConfiguration"/> for the host.</param>         /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>         public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)         {             _configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));             return this;         }          /// <summary>         /// Adds services to the container. This can be called multiple times and the results will be additive.         /// </summary>         /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used         /// to construct the <see cref="IConfiguration"/> for the host.</param>         /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>         public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)         {             _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));             return this;         }          /// <summary>         /// Overrides the factory used to create the service provider.         /// </summary>         /// <typeparam name="TContainerBuilder">The type of the builder to create.</typeparam>         /// <param name="factory">A factory used for creating service providers.</param>         /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>         public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)         {             _serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));             return this;         }          /// <summary>         /// Overrides the factory used to create the service provider.         /// </summary>         /// <param name="factory">A factory used for creating service providers.</param>         /// <typeparam name="TContainerBuilder">The type of the builder to create.</typeparam>         /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>         public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)         {             _serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(() => _hostBuilderContext, factory ?? throw new ArgumentNullException(nameof(factory)));             return this;         }          /// <summary>         /// Enables configuring the instantiated dependency container. This can be called multiple times and         /// the results will be additive.         /// </summary>         /// <typeparam name="TContainerBuilder">The type of the builder to create.</typeparam>         /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used         /// to construct the <see cref="IConfiguration"/> for the host.</param>         /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>         public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)         {             _configureContainerActions.Add(new ConfigureContainerAdapter<TContainerBuilder>(configureDelegate                 ?? throw new ArgumentNullException(nameof(configureDelegate))));             return this;         }          /// <summary>         /// Run the given actions to initialize the host. This can only be called once.         /// </summary>         /// <returns>An initialized <see cref="IHost"/></returns>         public IHost Build()         {             if (_hostBuilt)             {                 throw new InvalidOperationException("Build can only be called once.");             }             _hostBuilt = true;              BuildHostConfiguration();             CreateHostingEnvironment();             CreateHostBuilderContext();             BuildAppConfiguration();             CreateServiceProvider();              return _appServices.GetRequiredService<IHost>();         }          private void BuildHostConfiguration()         {             var configBuilder = new ConfigurationBuilder()                 .AddInMemoryCollection(); // Make sure there's some default storage since there are no default providers              foreach (var buildAction in _configureHostConfigActions)             {                 buildAction(configBuilder);             }             _hostConfiguration = configBuilder.Build();         }          private void CreateHostingEnvironment()         {             _hostingEnvironment = new HostingEnvironment()             {                 ApplicationName = _hostConfiguration[HostDefaults.ApplicationKey],                 EnvironmentName = _hostConfiguration[HostDefaults.EnvironmentKey] ?? Environments.Production,                 ContentRootPath = ResolveContentRootPath(_hostConfiguration[HostDefaults.ContentRootKey], AppContext.BaseDirectory),             };              if (string.IsNullOrEmpty(_hostingEnvironment.ApplicationName))             {                 // Note GetEntryAssembly returns null for the net4x console test runner.                 _hostingEnvironment.ApplicationName = Assembly.GetEntryAssembly()?.GetName().Name;             }              _hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath);         }          private string ResolveContentRootPath(string contentRootPath, string basePath)         {             if (string.IsNullOrEmpty(contentRootPath))             {                 return basePath;             }             if (Path.IsPathRooted(contentRootPath))             {                 return contentRootPath;             }             return Path.Combine(Path.GetFullPath(basePath), contentRootPath);         }          private void CreateHostBuilderContext()         {             _hostBuilderContext = new HostBuilderContext(Properties)             {                 HostingEnvironment = _hostingEnvironment,                 Configuration = _hostConfiguration             };         }          private void BuildAppConfiguration()         {             var configBuilder = new ConfigurationBuilder()                 .SetBasePath(_hostingEnvironment.ContentRootPath)                 .AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true);              foreach (var buildAction in _configureAppConfigActions)             {                 buildAction(_hostBuilderContext, configBuilder);             }             _appConfiguration = configBuilder.Build();             _hostBuilderContext.Configuration = _appConfiguration;         }          private void CreateServiceProvider()         {             var services = new ServiceCollection(); #pragma warning disable CS0618 // Type or member is obsolete             services.AddSingleton<IHostingEnvironment>(_hostingEnvironment); #pragma warning restore CS0618 // Type or member is obsolete             services.AddSingleton<IHostEnvironment>(_hostingEnvironment);             services.AddSingleton(_hostBuilderContext);             // register configuration as factory to make it dispose with the service provider             services.AddSingleton(_ => _appConfiguration); #pragma warning disable CS0618 // Type or member is obsolete             services.AddSingleton<IApplicationLifetime>(s => (IApplicationLifetime)s.GetService<IHostApplicationLifetime>()); #pragma warning restore CS0618 // Type or member is obsolete             services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>();             services.AddSingleton<IHostLifetime, ConsoleLifetime>();             services.AddSingleton<IHost, Internal.Host>();             services.AddOptions();             services.AddLogging();              foreach (var configureServicesAction in _configureServicesActions)             {                 configureServicesAction(_hostBuilderContext, services);             }              var containerBuilder = _serviceProviderFactory.CreateBuilder(services);              foreach (var containerAction in _configureContainerActions)             {                 containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);             }              _appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);              if (_appServices == null)             {                 throw new InvalidOperationException($"The IServiceProviderFactory returned a null IServiceProvider.");             }              // resolve configuration explicitly once to mark it as resolved within the             // service provider, ensuring it will be properly disposed with the provider             _ = _appServices.GetService<IConfiguration>();         }     } }

仔细阅读后可以发现其实 ConfigureAppConfiguration(...) 这个方法就只是将 Action<HostBuilderContext, IConfigurationBuilder> 类型的委托参数添加到 _configureAppConfigActions 这个集合中。

接着在构建的时候(即调用 Build() 方法时)调用 BuildAppConfiguration() 方法,该方法首先会去创建一个 ConfigurationBuilder 类型的对象 configBuilder ,然后遍历 _configureAppConfigActions 集合,执行该集合中所有的委托(其中调用委托时传递的参数之一就是这个 configBuilder 对象),最后调用 configBuilder.Build() 方法,该方法返回一个 ConfigurationRoot 类型的对象。

最终在调用 CreateServiceProvider() 方法时再将这个 ConfigurationRoot 类型的对象依赖注入给 IConfiguration

我们继续找到这个 ConfigurationBuilder 类的源码,如下所示:

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.Collections.Generic;  namespace Microsoft.Extensions.Configuration {     /// <summary>     /// Used to build key/value based configuration settings for use in an application.     /// </summary>     public class ConfigurationBuilder : IConfigurationBuilder     {         /// <summary>         /// Returns the sources used to obtain configuration values.         /// </summary>         public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();          /// <summary>         /// Gets a key/value collection that can be used to share data between the <see cref="IConfigurationBuilder"/>         /// and the registered <see cref="IConfigurationProvider"/>s.         /// </summary>         public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>();          /// <summary>         /// Adds a new configuration source.         /// </summary>         /// <param name="source">The configuration source to add.</param>         /// <returns>The same <see cref="IConfigurationBuilder"/>.</returns>         public IConfigurationBuilder Add(IConfigurationSource source)         {             if (source == null)             {                 throw new ArgumentNullException(nameof(source));             }              Sources.Add(source);             return this;         }          /// <summary>         /// Builds an <see cref="IConfiguration"/> with keys and values from the set of providers registered in         /// <see cref="Sources"/>.         /// </summary>         /// <returns>An <see cref="IConfigurationRoot"/> with keys and values from the registered providers.</returns>         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);         }     } }

从ConfigurationBuilder类中可以看出在调用该类 Build() 方法的时候它会去遍历 IList<IConfigurationSource> Sources 这个集合,那么这个集合里面存放的到底是什么数据,它又是什么时候加进去的呢?

其实我们在调用 hostBuilder.ConfigureAppConfiguration(...) 这个方法时传递的委托参数中的 config.AddJsonFile(...) 这个操作就是往 IList<IConfigurationSource> Sources 这个集合里面添加数据。

我们找到 Program.cs 类,将光标移动到 .AddJsonFile(...) 后按 F12 ,这样就可以定位找到 JsonConfigurationExtensions 类,如下所示:

ASP.NET Core中的配置Configuration的使用及其源码解析

ASP.NET Core中的配置Configuration的使用及其源码解析

接着我们去找到 Microsoft.Extensions.Configuration.JsonConfigurationExtensions 类的源码,如下所示:

ASP.NET Core中的配置Configuration的使用及其源码解析ASP.NET Core中的配置Configuration的使用及其源码解析

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.IO; using Microsoft.Extensions.Configuration.Json; using Microsoft.Extensions.FileProviders;  namespace Microsoft.Extensions.Configuration {     /// <summary>     /// Extension methods for adding <see cref="JsonConfigurationProvider"/>.     /// </summary>     public static class JsonConfigurationExtensions     {         /// <summary>         /// Adds the JSON configuration provider at <paramref name="path"/> to <paramref name="builder"/>.         /// </summary>         /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>         /// <param name="path">Path relative to the base path stored in          /// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param>         /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>         public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path)         {             return AddJsonFile(builder, provider: null, path: path, optional: false, reloadOnChange: false);         }          /// <summary>         /// Adds the JSON configuration provider at <paramref name="path"/> to <paramref name="builder"/>.         /// </summary>         /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>         /// <param name="path">Path relative to the base path stored in          /// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param>         /// <param name="optional">Whether the file is optional.</param>         /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>         public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional)         {             return AddJsonFile(builder, provider: null, path: path, optional: optional, reloadOnChange: false);         }          /// <summary>         /// Adds the JSON configuration provider at <paramref name="path"/> to <paramref name="builder"/>.         /// </summary>         /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>         /// <param name="path">Path relative to the base path stored in          /// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param>         /// <param name="optional">Whether the file is optional.</param>         /// <param name="reloadOnChange">Whether the configuration should be reloaded if the file changes.</param>         /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>         public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange)         {             return AddJsonFile(builder, provider: null, path: path, optional: optional, reloadOnChange: reloadOnChange);         }          /// <summary>         /// Adds a JSON configuration source to <paramref name="builder"/>.         /// </summary>         /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>         /// <param name="provider">The <see cref="IFileProvider"/> to use to access the file.</param>         /// <param name="path">Path relative to the base path stored in          /// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param>         /// <param name="optional">Whether the file is optional.</param>         /// <param name="reloadOnChange">Whether the configuration should be reloaded if the file changes.</param>         /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>         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();             });         }          /// <summary>         /// Adds a JSON configuration source to <paramref name="builder"/>.         /// </summary>         /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>         /// <param name="configureSource">Configures the source.</param>         /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>         public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource)             => builder.Add(configureSource);          /// <summary>         /// Adds a JSON configuration source to <paramref name="builder"/>.         /// </summary>         /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>         /// <param name="stream">The <see cref="Stream"/> to read the json configuration data from.</param>         /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>         public static IConfigurationBuilder AddJsonStream(this IConfigurationBuilder builder, Stream stream)         {             if (builder == null)             {                 throw new ArgumentNullException(nameof(builder));             }              return builder.Add<JsonStreamConfigurationSource>(s => s.Stream = stream);         }     } }

Microsoft.Extensions.Configuration.JsonConfigurationExtensions类源码

然后定位找到对应的 AddJsonFile 方法,如下所示:

/// <summary> /// Adds the JSON configuration provider at <paramref name="path"/> to <paramref name="builder"/>. /// </summary> /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param> /// <param name="path">Path relative to the base path stored in  /// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param> /// <param name="optional">Whether the file is optional.</param> /// <param name="reloadOnChange">Whether the configuration should be reloaded if the file changes.</param> /// <returns>The <see cref="IConfigurationBuilder"/>.</returns> public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange) {     return AddJsonFile(builder, provider: null, path: path, optional: optional, reloadOnChange: reloadOnChange); }

接着按 F12 会找到如下方法:

/// <summary> /// Adds a JSON configuration source to <paramref name="builder"/>. /// </summary> /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param> /// <param name="provider">The <see cref="IFileProvider"/> to use to access the file.</param> /// <param name="path">Path relative to the base path stored in  /// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param> /// <param name="optional">Whether the file is optional.</param> /// <param name="reloadOnChange">Whether the configuration should be reloaded if the file changes.</param> /// <returns>The <see cref="IConfigurationBuilder"/>.</returns> 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();     }); }

继续按 F12 找到如下方法:

/// <summary> /// Adds a JSON configuration source to <paramref name="builder"/>. /// </summary> /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param> /// <param name="configureSource">Configures the source.</param> /// <returns>The <see cref="IConfigurationBuilder"/>.</returns> public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource)     => builder.Add(configureSource);

可以发现它最终是调用 builder.Add(...) 这个扩展方法的,该扩展方法位于 Microsoft.Extensions.Configuration.ConfigurationExtensions 类中。

我们找到 ConfigurationExtensions 类的源码,如下所示:

ASP.NET Core中的配置Configuration的使用及其源码解析ASP.NET Core中的配置Configuration的使用及其源码解析

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.Collections.Generic; using System.Linq;  namespace Microsoft.Extensions.Configuration {     /// <summary>     /// Extension methods for configuration classes./>.     /// </summary>     public static class ConfigurationExtensions     {         /// <summary>         /// Adds a new configuration source.         /// </summary>         /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>         /// <param name="configureSource">Configures the source secrets.</param>         /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>         public static IConfigurationBuilder Add<TSource>(this IConfigurationBuilder builder, Action<TSource> configureSource) where TSource : IConfigurationSource, new()         {             var source = new TSource();             configureSource?.Invoke(source);             return builder.Add(source);         }          /// <summary>         /// Shorthand for GetSection("ConnectionStrings")[name].         /// </summary>         /// <param name="configuration">The configuration.</param>         /// <param name="name">The connection string key.</param>         /// <returns>The connection string.</returns>         public static string GetConnectionString(this IConfiguration configuration, string name)         {             return configuration?.GetSection("ConnectionStrings")?[name];         }          /// <summary>         /// Get the enumeration of key value pairs within the <see cref="IConfiguration" />         /// </summary>         /// <param name="configuration">The <see cref="IConfiguration"/> to enumerate.</param>         /// <returns>An enumeration of key value pairs.</returns>         public static IEnumerable<KeyValuePair<string, string>> AsEnumerable(this IConfiguration configuration) => configuration.AsEnumerable(makePathsRelative: false);          /// <summary>         /// Get the enumeration of key value pairs within the <see cref="IConfiguration" />         /// </summary>         /// <param name="configuration">The <see cref="IConfiguration"/> to enumerate.</param>         /// <param name="makePathsRelative">If true, the child keys returned will have the current configuration's Path trimmed from the front.</param>         /// <returns>An enumeration of key value pairs.</returns>         public static IEnumerable<KeyValuePair<string, string>> AsEnumerable(this IConfiguration configuration, bool makePathsRelative)         {             var stack = new Stack<IConfiguration>();             stack.Push(configuration);             var rootSection = configuration as IConfigurationSection;             var prefixLength = (makePathsRelative && rootSection != null) ? rootSection.Path.Length + 1 : 0;             while (stack.Count > 0)             {                 var config = stack.Pop();                 // Don't include the sections value if we are removing paths, since it will be an empty key                 if (config is IConfigurationSection section && (!makePathsRelative || config != configuration))                 {                     yield return new KeyValuePair<string, string>(section.Path.Substring(prefixLength), section.Value);                 }                 foreach (var child in config.GetChildren())                 {                     stack.Push(child);                 }             }         }          /// <summary>         /// Determines whether the section has a <see cref="IConfigurationSection.Value"/> or has children         /// </summary>         public static bool Exists(this IConfigurationSection section)         {             if (section == null)             {                 return false;             }             return section.Value != null || section.GetChildren().Any();         }     } }

Microsoft.Extensions.Configuration.ConfigurationExtensions类源码

从中找到对应的扩展方法:

/// <summary> /// Adds a new configuration source. /// </summary> /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param> /// <param name="configureSource">Configures the source secrets.</param> /// <returns>The <see cref="IConfigurationBuilder"/>.</returns> public static IConfigurationBuilder Add<TSource>(this IConfigurationBuilder builder, Action<TSource> configureSource) where TSource : IConfigurationSource, new() {     var source = new TSource();     configureSource?.Invoke(source);     return builder.Add(source); }

由此可见 config.AddJsonFile(...) 这个方法最终会变成调用 ConfigurationBuilder 类中的 Add(...) 方法,传递 JsonConfigurationSource 类型的对象。

下面我们继续往下分析 ConfigurationBuilder 类的源码:

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.Collections.Generic;  namespace Microsoft.Extensions.Configuration {     /// <summary>     /// Used to build key/value based configuration settings for use in an application.     /// </summary>     public class ConfigurationBuilder : IConfigurationBuilder     {         /// <summary>         /// Returns the sources used to obtain configuration values.         /// </summary>         public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();          /// <summary>         /// Gets a key/value collection that can be used to share data between the <see cref="IConfigurationBuilder"/>         /// and the registered <see cref="IConfigurationProvider"/>s.         /// </summary>         public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>();          /// <summary>         /// Adds a new configuration source.         /// </summary>         /// <param name="source">The configuration source to add.</param>         /// <returns>The same <see cref="IConfigurationBuilder"/>.</returns>         public IConfigurationBuilder Add(IConfigurationSource source)         {             if (source == null)             {                 throw new ArgumentNullException(nameof(source));             }              Sources.Add(source);             return this;         }          /// <summary>         /// Builds an <see cref="IConfiguration"/> with keys and values from the set of providers registered in         /// <see cref="Sources"/>.         /// </summary>         /// <returns>An <see cref="IConfigurationRoot"/> with keys and values from the registered providers.</returns>         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);         }     } }

从上文的分析中我们已经知道此时 IList<IConfigurationSource> Sources 这个集合中存放的其实就是 JsonConfigurationSource 类型的对象。

我们找到 JsonConfigurationSource 类的源码:

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System;  namespace Microsoft.Extensions.Configuration.Json {     /// <summary>     /// Represents a JSON file as an <see cref="IConfigurationSource"/>.     /// </summary>     public class JsonConfigurationSource : FileConfigurationSource     {         /// <summary>         /// Builds the <see cref="JsonConfigurationProvider"/> for this source.         /// </summary>         /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>         /// <returns>A <see cref="JsonConfigurationProvider"/></returns>         public override IConfigurationProvider Build(IConfigurationBuilder builder)         {             EnsureDefaults(builder);             return new JsonConfigurationProvider(this);         }     } }

从中我们可以知道 JsonConfigurationSource 中的 Build(...) 方法返回的是 JsonConfigurationProvider 类型的对象。

至此,我们就清楚了 ConfigurationBuilder 类中的 Build(...) 方法返回的是 new ConfigurationRoot(providers) ,而 providers 集合里面存放的就是 JsonConfigurationProvider 类型的对象。

我们继续找到 ConfigurationRoot 类的源码,如下所示:

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Microsoft.Extensions.Primitives;  namespace Microsoft.Extensions.Configuration {     /// <summary>     /// The root node for a configuration.     /// </summary>     public class ConfigurationRoot : IConfigurationRoot, IDisposable     {         private readonly IList<IConfigurationProvider> _providers;         private readonly IList<IDisposable> _changeTokenRegistrations;         private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();          /// <summary>         /// Initializes a Configuration root with a list of providers.         /// </summary>         /// <param name="providers">The <see cref="IConfigurationProvider"/>s for this configuration.</param>         public ConfigurationRoot(IList<IConfigurationProvider> providers)         {             if (providers == null)             {                 throw new ArgumentNullException(nameof(providers));             }              _providers = providers;             _changeTokenRegistrations = new List<IDisposable>(providers.Count);             foreach (var p in providers)             {                 p.Load();                 _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));             }         }          /// <summary>         /// The <see cref="IConfigurationProvider"/>s for this configuration.         /// </summary>         public IEnumerable<IConfigurationProvider> Providers => _providers;          /// <summary>         /// Gets or sets the value corresponding to a configuration key.         /// </summary>         /// <param name="key">The configuration key.</param>         /// <returns>The configuration value.</returns>         public string this[string key]         {             get             {                 for (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);                 }             }         }          /// <summary>         /// Gets the immediate children sub-sections.         /// </summary>         /// <returns>The children.</returns>         public IEnumerable<IConfigurationSection> GetChildren() => this.GetChildrenImplementation(null);          /// <summary>         /// Returns a <see cref="IChangeToken"/> that can be used to observe when this configuration is reloaded.         /// </summary>         /// <returns>The <see cref="IChangeToken"/>.</returns>         public IChangeToken GetReloadToken() => _changeToken;          /// <summary>         /// Gets a configuration sub-section with the specified key.         /// </summary>         /// <param name="key">The key of the configuration section.</param>         /// <returns>The <see cref="IConfigurationSection"/>.</returns>         /// <remarks>         ///     This method will never return <c>null</c>. If no matching sub-section is found with the specified key,         ///     an empty <see cref="IConfigurationSection"/> will be returned.         /// </remarks>         public IConfigurationSection GetSection(string key)             => new ConfigurationSection(this, key);          /// <summary>         /// Force the configuration values to be reloaded from the underlying sources.         /// </summary>         public void Reload()         {             foreach (var provider in _providers)             {                 provider.Load();             }             RaiseChanged();         }          private void RaiseChanged()         {             var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());             previousToken.OnReload();         }          /// <inheritdoc />         public void Dispose()         {             // dispose change token registrations             foreach (var registration in _changeTokenRegistrations)             {                 registration.Dispose();             }              // dispose providers             foreach (var provider in _providers)             {                 (provider as IDisposable)?.Dispose();             }         }     } }

其中 ConfigurationSection 类的源码如下:

ASP.NET Core中的配置Configuration的使用及其源码解析ASP.NET Core中的配置Configuration的使用及其源码解析

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.Collections.Generic; using Microsoft.Extensions.Primitives;  namespace Microsoft.Extensions.Configuration {     /// <summary>     /// Represents a section of application configuration values.     /// </summary>     public class ConfigurationSection : IConfigurationSection     {         private readonly IConfigurationRoot _root;         private readonly string _path;         private string _key;          /// <summary>         /// Initializes a new instance.         /// </summary>         /// <param name="root">The configuration root.</param>         /// <param name="path">The path to this section.</param>         public ConfigurationSection(IConfigurationRoot root, string path)         {             if (root == null)             {                 throw new ArgumentNullException(nameof(root));             }              if (path == null)             {                 throw new ArgumentNullException(nameof(path));             }              _root = root;             _path = path;         }          /// <summary>         /// Gets the full path to this section from the <see cref="IConfigurationRoot"/>.         /// </summary>         public string Path => _path;          /// <summary>         /// Gets the key this section occupies in its parent.         /// </summary>         public string Key         {             get             {                 if (_key == null)                 {                     // Key is calculated lazily as last portion of Path                     _key = ConfigurationPath.GetSectionKey(_path);                 }                 return _key;             }         }          /// <summary>         /// Gets or sets the section value.         /// </summary>         public string Value         {             get             {                 return _root[Path];             }             set             {                 _root[Path] = value;             }         }          /// <summary>         /// Gets or sets the value corresponding to a configuration key.         /// </summary>         /// <param name="key">The configuration key.</param>         /// <returns>The configuration value.</returns>         public string this[string key]         {             get             {                 return _root[ConfigurationPath.Combine(Path, key)];             }              set             {                 _root[ConfigurationPath.Combine(Path, key)] = value;             }         }          /// <summary>         /// Gets a configuration sub-section with the specified key.         /// </summary>         /// <param name="key">The key of the configuration section.</param>         /// <returns>The <see cref="IConfigurationSection"/>.</returns>         /// <remarks>         ///     This method will never return <c>null</c>. If no matching sub-section is found with the specified key,         ///     an empty <see cref="IConfigurationSection"/> will be returned.         /// </remarks>         public IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key));          /// <summary>         /// Gets the immediate descendant configuration sub-sections.         /// </summary>         /// <returns>The configuration sub-sections.</returns>         public IEnumerable<IConfigurationSection> GetChildren() => _root.GetChildrenImplementation(Path);          /// <summary>         /// Returns a <see cref="IChangeToken"/> that can be used to observe when this configuration is reloaded.         /// </summary>         /// <returns>The <see cref="IChangeToken"/>.</returns>         public IChangeToken GetReloadToken() => _root.GetReloadToken();     } }

Microsoft.Extensions.Configuration.ConfigurationSection类源码

仔细观察上面的源码后我们可以发现:

1、在ConfigurationRoot类的构造函数中它会依次调用传递过来的Provider的Load方法去加载数据。

2、使用ConfigurationRoot索引器取数据的时候,它会逆序依次调用Provider的TryGet方法获取数据,如果成功就直接返回,这就是为什么后添加的数据源会覆盖之前添加的数据源

3、在创建ConfigurationSection的时候会把ConfigurationRoot传递进去,而ConfigurationSection取数据的时候也是通过ConfigurationRoot来取的,实际上ConfigurationSection并不包含任何实际的配置数据。

接下来我们就来看一下它是如何加载数据的,找到 JsonConfigurationProvider 类的源码,如下所示:

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.Collections.Generic; using System.IO; using System.Text.Json;  namespace Microsoft.Extensions.Configuration.Json {     /// <summary>     /// A JSON file based <see cref="FileConfigurationProvider"/>.     /// </summary>     public class JsonConfigurationProvider : FileConfigurationProvider     {         /// <summary>         /// Initializes a new instance with the specified source.         /// </summary>         /// <param name="source">The source settings.</param>         public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }          /// <summary>         /// Loads the JSON data from a stream.         /// </summary>         /// <param name="stream">The stream to read.</param>         public override void Load(Stream stream)         {             try             {                 Data = JsonConfigurationFileParser.Parse(stream);             }             catch (JsonException e)             {                 throw new FormatException(Resources.Error_JSONParseError, e);             }         }     } }

它是继承 FileConfigurationProvider 类,我们找到 FileConfigurationProvider 类的源码:

ASP.NET Core中的配置Configuration的使用及其源码解析ASP.NET Core中的配置Configuration的使用及其源码解析

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.Collections.Generic; using System.IO; using System.Runtime.ExceptionServices; using System.Text; using System.Threading; using Microsoft.Extensions.Primitives;  namespace Microsoft.Extensions.Configuration {     /// <summary>     /// Base class for file based <see cref="ConfigurationProvider"/>.     /// </summary>     public abstract class FileConfigurationProvider : ConfigurationProvider, IDisposable     {         private readonly IDisposable _changeTokenRegistration;          /// <summary>         /// Initializes a new instance with the specified source.         /// </summary>         /// <param name="source">The source settings.</param>         public FileConfigurationProvider(FileConfigurationSource source)         {             if (source == null)             {                 throw new ArgumentNullException(nameof(source));             }             Source = source;              if (Source.ReloadOnChange && Source.FileProvider != null)             {                 _changeTokenRegistration = ChangeToken.OnChange(                     () => Source.FileProvider.Watch(Source.Path),                     () => {                         Thread.Sleep(Source.ReloadDelay);                         Load(reload: true);                     });             }         }          /// <summary>         /// The source settings for this provider.         /// </summary>         public FileConfigurationSource Source { get; }                  /// <summary>         /// Generates a string representing this provider name and relevant details.         /// </summary>         /// <returns> The configuration name. </returns>         public override string ToString()             => $"{GetType().Name} for '{Source.Path}' ({(Source.Optional ? "Optional" : "Required")})";          private void Load(bool reload)         {             var file = Source.FileProvider?.GetFileInfo(Source.Path);             if (file == null || !file.Exists)             {                 if (Source.Optional || reload) // Always optional on reload                 {                     Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);                 }                 else                 {                     var error = new StringBuilder($"The configuration file '{Source.Path}' was not found and is not optional.");                     if (!string.IsNullOrEmpty(file?.PhysicalPath))                     {                         error.Append($" The physical path is '{file.PhysicalPath}'.");                     }                     HandleException(ExceptionDispatchInfo.Capture(new FileNotFoundException(error.ToString())));                 }             }             else             {                 // Always create new Data on reload to drop old keys                 if (reload)                 {                     Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);                 }                 using (var stream = file.CreateReadStream())                 {                     try                     {                         Load(stream);                     }                     catch (Exception e)                     {                         HandleException(ExceptionDispatchInfo.Capture(e));                     }                 }             }             // REVIEW: Should we raise this in the base as well / instead?             OnReload();         }          /// <summary>         /// Loads the contents of the file at <see cref="Path"/>.         /// </summary>         /// <exception cref="FileNotFoundException">If Optional is <c>false</c> on the source and a         /// file does not exist at specified Path.</exception>         public override void Load()         {             Load(reload: false);         }          /// <summary>         /// Loads this provider's data from a stream.         /// </summary>         /// <param name="stream">The stream to read.</param>         public abstract void Load(Stream stream);          private void HandleException(ExceptionDispatchInfo info)         {             bool ignoreException = false;             if (Source.OnLoadException != null)             {                 var exceptionContext = new FileLoadExceptionContext                 {                     Provider = this,                     Exception = info.SourceException                 };                 Source.OnLoadException.Invoke(exceptionContext);                 ignoreException = exceptionContext.Ignore;             }             if (!ignoreException)             {                 info.Throw();             }         }          /// <inheritdoc />         public void Dispose() => Dispose(true);          /// <summary>         /// Dispose the provider.         /// </summary>         /// <param name="disposing"><c>true</c> if invoked from <see cref="IDisposable.Dispose"/>.</param>         protected virtual void Dispose(bool disposing)         {             _changeTokenRegistration?.Dispose();         }     } }

Microsoft.Extensions.Configuration.FileConfigurationProvider类源码

仔细阅读 JsonConfigurationProvider 和 FileConfigurationProvider 这两个类的源码后,可以知道它最终是通过 JsonConfigurationFileParser.Parse(stream) 这个操作来加载数据的。

并且 FileConfigurationProvider 类继承自 ConfigurationProvider 类,我们找到 ConfigurationProvider 和 JsonConfigurationFileParser 这两个类的源码,如下所示:

ConfigurationProvider类源码如下:

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Microsoft.Extensions.Primitives;  namespace Microsoft.Extensions.Configuration {     /// <summary>     /// Base helper class for implementing an <see cref="IConfigurationProvider"/>     /// </summary>     public abstract class ConfigurationProvider : IConfigurationProvider     {         private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();          /// <summary>         /// Initializes a new <see cref="IConfigurationProvider"/>         /// </summary>         protected ConfigurationProvider()         {             Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);         }          /// <summary>         /// The configuration key value pairs for this provider.         /// </summary>         protected IDictionary<string, string> Data { get; set; }          /// <summary>         /// Attempts to find a value with the given key, returns true if one is found, false otherwise.         /// </summary>         /// <param name="key">The key to lookup.</param>         /// <param name="value">The value found at key if one is found.</param>         /// <returns>True if key has a value, false otherwise.</returns>         public virtual bool TryGet(string key, out string value)             => Data.TryGetValue(key, out value);          /// <summary>         /// Sets a value for a given key.         /// </summary>         /// <param name="key">The configuration key to set.</param>         /// <param name="value">The value to set.</param>         public virtual void Set(string key, string value)             => Data[key] = value;          /// <summary>         /// Loads (or reloads) the data for this provider.         /// </summary>         public virtual void Load()         { }          /// <summary>         /// Returns the list of keys that this provider has.         /// </summary>         /// <param name="earlierKeys">The earlier keys that other providers contain.</param>         /// <param name="parentPath">The path for the parent IConfiguration.</param>         /// <returns>The list of keys for this provider.</returns>         public virtual IEnumerable<string> GetChildKeys(             IEnumerable<string> earlierKeys,             string parentPath)         {             var prefix = parentPath == null ? string.Empty : parentPath + ConfigurationPath.KeyDelimiter;              return Data                 .Where(kv => kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))                 .Select(kv => Segment(kv.Key, prefix.Length))                 .Concat(earlierKeys)                 .OrderBy(k => k, ConfigurationKeyComparer.Instance);         }          private static string Segment(string key, int prefixLength)         {             var indexOf = key.IndexOf(ConfigurationPath.KeyDelimiter, prefixLength, StringComparison.OrdinalIgnoreCase);             return indexOf < 0 ? key.Substring(prefixLength) : key.Substring(prefixLength, indexOf - prefixLength);         }          /// <summary>         /// Returns a <see cref="IChangeToken"/> that can be used to listen when this provider is reloaded.         /// </summary>         /// <returns>The <see cref="IChangeToken"/>.</returns>         public IChangeToken GetReloadToken()         {             return _reloadToken;         }          /// <summary>         /// Triggers the reload change token and creates a new one.         /// </summary>         protected void OnReload()         {             var previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());             previousToken.OnReload();         }          /// <summary>         /// Generates a string representing this provider name and relevant details.         /// </summary>         /// <returns> The configuration name. </returns>         public override string ToString() => $"{GetType().Name}";     } }

JsonConfigurationFileParser类源码如下:

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json;  namespace Microsoft.Extensions.Configuration.Json {     internal class JsonConfigurationFileParser     {         private JsonConfigurationFileParser() { }          private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);         private readonly Stack<string> _context = new Stack<string>();         private string _currentPath;          public static IDictionary<string, string> Parse(Stream input)             => new JsonConfigurationFileParser().ParseStream(input);          private IDictionary<string, string> ParseStream(Stream input)         {             _data.Clear();              var jsonDocumentOptions = new JsonDocumentOptions             {                 CommentHandling = JsonCommentHandling.Skip,                 AllowTrailingCommas = true,             };              using (var reader = new StreamReader(input))             using (JsonDocument doc = JsonDocument.Parse(reader.ReadToEnd(), jsonDocumentOptions))             {                 if (doc.RootElement.ValueKind != JsonValueKind.Object)                 {                     throw new FormatException(Resources.FormatError_UnsupportedJSONToken(doc.RootElement.ValueKind));                 }                 VisitElement(doc.RootElement);             }              return _data;         }          private void VisitElement(JsonElement element) {             foreach (var property in element.EnumerateObject())             {                 EnterContext(property.Name);                 VisitValue(property.Value);                 ExitContext();             }         }          private void VisitValue(JsonElement value)         {             switch (value.ValueKind) {                 case JsonValueKind.Object:                     VisitElement(value);                     break;                  case JsonValueKind.Array:                     var index = 0;                     foreach (var arrayElement in value.EnumerateArray()) {                         EnterContext(index.ToString());                         VisitValue(arrayElement);                         ExitContext();                         index++;                     }                     break;                  case JsonValueKind.Number:                 case JsonValueKind.String:                 case JsonValueKind.True:                 case JsonValueKind.False:                 case JsonValueKind.Null:                     var key = _currentPath;                     if (_data.ContainsKey(key))                     {                         throw new FormatException(Resources.FormatError_KeyIsDuplicated(key));                     }                     _data[key] = value.ToString();                     break;                  default:                     throw new FormatException(Resources.FormatError_UnsupportedJSONToken(value.ValueKind));             }         }          private void EnterContext(string context)         {             _context.Push(context);             _currentPath = ConfigurationPath.Combine(_context.Reverse());         }          private void ExitContext()         {             _context.Pop();             _currentPath = ConfigurationPath.Combine(_context.Reverse());         }     } }

其中ConfigurationPath类源码如下:

ASP.NET Core中的配置Configuration的使用及其源码解析ASP.NET Core中的配置Configuration的使用及其源码解析

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.  using System; using System.Collections.Generic;  namespace Microsoft.Extensions.Configuration {     /// <summary>     /// Utility methods and constants for manipulating Configuration paths     /// </summary>     public static class ConfigurationPath     {         /// <summary>         /// The delimiter ":" used to separate individual keys in a path.         /// </summary>         public static readonly string KeyDelimiter = ":";          /// <summary>         /// Combines path segments into one path.         /// </summary>         /// <param name="pathSegments">The path segments to combine.</param>         /// <returns>The combined path.</returns>         public static string Combine(params string[] pathSegments)         {             if (pathSegments == null)             {                 throw new ArgumentNullException(nameof(pathSegments));             }             return string.Join(KeyDelimiter, pathSegments);         }          /// <summary>         /// Combines path segments into one path.         /// </summary>         /// <param name="pathSegments">The path segments to combine.</param>         /// <returns>The combined path.</returns>         public static string Combine(IEnumerable<string> pathSegments)         {             if (pathSegments == null)             {                 throw new ArgumentNullException(nameof(pathSegments));             }             return string.Join(KeyDelimiter, pathSegments);         }          /// <summary>         /// Extracts the last path segment from the path.         /// </summary>         /// <param name="path">The path.</param>         /// <returns>The last path segment of the path.</returns>         public static string GetSectionKey(string path)         {             if (string.IsNullOrEmpty(path))             {                 return path;             }              var lastDelimiterIndex = path.LastIndexOf(KeyDelimiter, StringComparison.OrdinalIgnoreCase);             return lastDelimiterIndex == -1 ? path : path.Substring(lastDelimiterIndex + 1);         }          /// <summary>         /// Extracts the path corresponding to the parent node for a given path.         /// </summary>         /// <param name="path">The path.</param>         /// <returns>The original path minus the last individual segment found in it. Null if the original path corresponds to a top level node.</returns>         public static string GetParentPath(string path)         {             if (string.IsNullOrEmpty(path))             {                 return null;             }              var lastDelimiterIndex = path.LastIndexOf(KeyDelimiter, StringComparison.OrdinalIgnoreCase);             return lastDelimiterIndex == -1 ? null : path.Substring(0, lastDelimiterIndex);         }     } }

Microsoft.Extensions.Configuration.ConfigurationPath类源码

仔细阅读与ConfigurationProvider类相关的源码后可以发现, 其实ConfigurationProvider只是一个抽象类,具体的需要由对应类型的Provider去实现,其实也只是需要实现Load方法,去填充那个字符串字典。而在实现的过程中需要用到ConfigurationPath这个静态类来帮助生成字典里的key,具体来说就是各层级之间用冒号 “:” 隔开,例如:“A:B:C” 。这个冒号是以静态只读的形式定义在ConfigurationPath类中的。

3、小结

四大配置对象的UML图如下所示:

ASP.NET Core中的配置Configuration的使用及其源码解析

 

1、IConfigurationBuilder:整个配置系统的核心,包含多个IConfigurationSource,利用它们产生多个IConfigurationProvider,最终是为了得到一个IConfigurationRoot对象,并将它注入到容器中。

2、IConfigurationProvider:实际包含配置信息的类,内部包含一个字符串字典,它由IConfigurationSource产生。

3、IConfigurationSource:包含了一个配置源的信息,以文件为例,包含了文件名和文件路径等,并不包含实际的配置信息。如果是基于数据库的数据源,它会包含数据库连接信息,SQL等。它的目的是为了产生一个IConfigurationProvider。

4、IConfigurationRoot:在获取配置信息时,直接操作的对象,内部包含一个IConfigurationProvider列表。

大致过程:首先是调用各类数据源类库提供的扩展方法往IConfigurationBuilder中添加数据源IConfigurationSource,之后IConfigurationBuilder会依次调用IConfigurationSourceBuild方法产生对应的IConfigurationProvider,并将他们传入到IConfigurationRoot中,最终我们会拿到IConfigurationRoot进行使用。

本文部分内容参考博文:https://www.cnblogs.com/zhurongbo/p/10831284.html

至此本文就全部介绍完了,如果觉得对您有所启发请记得点个赞哦!!! 

 

aspnetcore源码:

链接:https://pan.baidu.com/s/1fszyRzDw9di1hVvPIF4VYg  提取码:wz1j

Demo源码:

链接:https://pan.baidu.com/s/1AdV-cwWnIKNx81WQqEXUdg  提取码:ki9c

此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/15916859.html

版权声明:如有雷同纯属巧合,如有侵权请及时联系本人修改,谢谢!!!