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


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

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

    Demo的目录结构如下所示:

    本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 运行结果如下:

    2、IConfiguration 源码解析

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

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

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

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

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

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

    我们从 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 类,如下所示:

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

    // 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 类的源码,如下所示:

    // 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 类的源码如下:

    // 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 类的源码:

    // 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类源码如下:

    // 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图如下所示:

    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

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

  • 相关阅读:
    使用 cordova-plugin-wechat 分享返回后闪退解决方法
    恢复删除的表
    移动端还原设计图
    js文本差异比较
    windows使用nvm安装nodejs后升级npm报错
    windows添加右键菜单"在此处打开cmd窗口"
    cordova热更新
    js变量提升
    c# 判断字符串是否是日期格式需要注意的一点小问题
    剑指offer_和为S的两个数字
  • 原文地址:https://www.cnblogs.com/xyh9039/p/15916859.html
Copyright © 2020-2023  润新知