一、数据源的注册
在上一节介绍的数据源设置中,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集和中。