ASP.NET Core : 二十三. 深入聊一聊配置的内部处理机制(一)

一、数据源的注册

在上一节介绍的数据源设置中,appsettings.json、命令行、环境变量三种方式是被系统自动加载的,这是因为系统在webHost.CreateDefaultBuilder(args)中已经为这三种数据源进了注册,那么就从这个方法说起。这个方法中同样调用了ConfigureAppConfiguration方法,代码如下:

public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
    var builder = newWebHostBuilder();
    //省略部分代码
    builder.UseKestrel((builderContext, options) =>
        {
            options.Configure(builderContext.Configuration.GetSection("Kestrel"));
        })
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            var env = hostingContext.HostingEnvironment;
            config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional:true, reloadOnChange: true);
            if(env.IsDevelopment())
            {
                var appAssembly = Assembly.Load(newAssemblyName(env.ApplicationName));
                if(appAssembly != null)
                {
                    config.AddUserSecrets(appAssembly, optional: true);
                }
            }
 
            config.AddEnvironmentVariables();
            if(args != null)
            {
                config.AddCommandLine(args);
            }
       })
 
       //省略部分代码
 
    return builder;
}

看一下其中的ConfigureAppConfiguration方法,加载的内容主要有四种,首先加载的是appsettings.json和appsettings.{env.EnvironmentName}.json两个JSON文件,关于env.EnvironmentName在前面的章节已经说过,常见的有Development、Staging 和 Production三种值,在我们开发调试时一般是Development,也就是会加载appsettings.json和appsettings. Development.json两个JSON文件。第二种加载的是用户机密文件,这仅限于Development状态下,会通过config.AddUserSecrets方法加载。第三种是通过config.AddEnvironmentVariables方法加载的环境变量,第四种是通过config.AddCommandLine方法加载的命令行参数。


注意:这里的ConfigureAppConfiguration方法这时候是不会被执行的,只是将这个方法作为一个Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate添加到了WebHostBuilder的_configureServicesDelegates属性中。configureServicesDelegates是一个List<Action<WebHostBuilderContext, IConfigurationBuilder>>类型的集合。对应代码如下:

public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate)
{
    if(configureDelegate == null)
    {
        throw new ArgumentNullException(nameof(configureDelegate));
    }
 
    _configureAppConfigurationBuilderDelegates.Add(configureDelegate);
    returnthis;
}

上一节的例子中,我们在webHost.CreateDefaultBuilder(args)方法之后再次调用ConfigureAppConfiguration方法添加了一些自定义的数据源,这个方法也是没有执行,同样被添加到了这个集合中。直到WebHostBuilder通过它的Build()方法创建WebHost的时候,才会遍历这个集合逐一执行。这段代码写在被Build()方法调用的BuildCommonServices()中:

private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
    //省略部分代码
    var builder = new ConfigurationBuilder()
        .SetBasePath(_hostingEnvironment.ContentRootPath)
        .AddConfiguration(_config);
 
    foreach (var configureAppConfiguration in _configureAppConfigurationBuilderDelegates)
    {
        configureAppConfiguration(_context, builder);
    }
 
    var configuration = builder.Build();
    services.AddSingleton<IConfiguration>(configuration);
    _context.Configuration = configuration;
//省略部分代码
    return services;
}

首先创建了一个ConfigurationBuilder对象,然后通过foreach循环逐一执行被添加到集合_configureAppConfigurationBuilderDelegates中的configureAppConfiguration方法,那么在执行的时候,这些不同的数据源是如何被加载的呢?这部分功能在namespace Microsoft.Extensions.Configuration命名空间中。


以appsettings.json对应的config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)方法为例,进一步看一下它的实现方式。首先介绍的是IConfigurationBuilder接口,对应的实现类是ConfigurationBuilder,代码如下:

public class ConfigurationBuilder : IConfigurationBuilder
    {
        public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();
 
        public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>();
 
        public IConfigurationBuilder Add(IConfigurationSource source)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
 
            Sources.Add(source);
            return this;
        }
        //省略了IConfigurationRoot Build()方法,下文介绍
    }

ConfigureAppConfiguration方法中调用的AddJsonFile方法来自JsonConfigurationExtensions类,代码如下:

public static class JsonConfigurationExtensions
{
//省略部分代码
 
    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();
        });
    }
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource)
        => builder.Add(configureSource);
}

AddJsonFile方法会创建一个JsonConfigurationSource并通过ConfigurationBuilder的Add(IConfigurationSource source)方法将这个JsonConfigurationSource添加到ConfigurationBuilder的IList<IConfigurationSource> Sources集和中去。


同理,针对环境变量,存在对应的EnvironmentVariablesExtensions,会创建一个对应的EnvironmentVariablesConfigurationSource添加到ConfigurationBuilder的IList<IConfigurationSource> Sources集和中去。这样的还有CommandLineConfigurationExtensions和CommandLineConfigurationSource等,最终结果就是会根据数据源的加载顺序,生成多个XXXConfigurationSource对象(它们都直接或间接实现了IConfigurationSource接口)添加到ConfigurationBuilder的IList<IConfigurationSource> Sources集和中。


上一篇:Linux如何查看当前进程的ID


下一篇:ASP.NET Core : 二十三. 深入聊一聊配置的内部处理机制(三)