龙空技术网

ASP.NET Core 5-配置系统设计

程序员专栏 681

前言:

现时咱们对“net core 配置”大体比较注重,姐妹们都需要学习一些“net core 配置”的相关资讯。那么小编在网络上汇集了一些有关“net core 配置””的相关内容,希望同学们能喜欢,姐妹们一起来了解一下吧!

今天我们来讲下AspNetCore5中的配置文件读取设计。

首先抛出个问题,如果要我们来设计一个通过的配置文件读取系统,我们应该要怎么设计类呢?传统的思路可以是设计一个文件读取类,包含文件路径,Get,Set和Init方法,用于初始化文件读取并获取值,不同的文件类型设计不同的文件读取类。那么在AspNetCore中是如何设计的呢?

在AspNetCore中是通过Configuration配置模式来实现多种文件类型的配置读取的。大体的思路,与我们上面提到的差不多。

首先我们来看下Configuration配置模式概览图:

以下是几个重要的类:

ConfigurationBuilder:ConfigurationBuilder类用于在应用启动时添加相应的ConfigurationSource,例如在应用启动时通过的扩展方法添加:AddJson(“appsettings.json”)或者AddCommandLine(args).ConfigurationSource: ConfigurationSource是配置数据源,对于每种不同类型的配置文件提供了不同的配置提供者。ConfigurationProvider: 配置提供者。用于根据配置文件初始化配置值,并将配置键值对形成Key:Value的形式。ConfigurationRoot: 配置根。由于应用可以添加多种程序源,所有持有了这些数据源。并提供了获取和设置键值的方法。获取值,倒序遍历所有的ConfigurationProvider来获取相应Key表示的值。一旦某一个配置提供者有值,就返回该值。注意,后加入的ConfigurationProvider将覆盖前面的配置值。设置值,遍历所有的ConfigurationProvider,并将key所代表的value设置成这个值。ConfigurationSection:配置节点类。用于获取相应key节点下的所有子节点配置项。为ConfigurationRoot提供获取子配置节点的功能。

Configuration模式UML图:

源码解析:

首先来看下源码流程概览:

AspNetCore中,配置模式是在Program.cs类中的Host.CreateDefaultBuilder方法中调用扩展方法来注入ConfigurationSource和并在Build的时候将配置源注入到容器的。

接下来以添加环境变量参数为例。

让我们以Program.cs开始:

public class Program{		public static void Main(string[] args)		{				CreateHostBuilder(args).Build().Run();		}		public static IHostBuilder CreateHostBuilder(string[] args) =>			Host.CreateDefaultBuilder(args)			.ConfigureWebHostDefaults(webBuilder =>			{				webBuilder.UseStartup<Startup>();			});}

在Host.CreateDefaultBuilder方法中:

//Microsoft.Extensions.Hosting.cspublic static IHostBuilder CreateDefaultBuilder(string[] args){		HostBuilder hostBuilder = new HostBuilder();		......		hostBuilder.ConfigureHostConfiguration((Action<IConfigurationBuilder>) (config =>		{				config.AddEnvironmentVariables("DOTNET_");				......		}));		......		return (IHostBuilder) hostBuilder;}

IConfigurationBuilder的AddEnvironmentVariables扩展方法为ConfigurationBuilder添加了环境变量的支持。

//Microsoft.Extensions.Configuration.EnvironmentVariablesExtensions.cspublic static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder,string prefix){		configurationBuilder.Add(new EnvironmentVariablesConfigurationSource()		{				Prefix = prefix		});		return configurationBuilder;}

AddEnvironmentVariables方法添加了EnvironmentVariablesConfigurationSource环境变量数据源,并通过构造传参来确定所要获取的环境变量的前缀。

本例中,构造函数传递的是”DOTNET_”字符串,表示获取环境变量中以”DOTNET_”开头的环境变量。

Ok,现在框架已经把EnvironmentVariablesConfigurationSource数据源加入到了ConfigurationBuilder中,前面的概览图我们知道框架在加入完后会调用ConfigurationBuilder的Builder方法来初始化数据源。

//Microsoft.Extensions.Hosting.HostBuilder.csprivate void BuildAppConfiguration(){          IConfigurationBuilder configBuilder = new ConfigurationBuilder()            .SetBasePath(_hostingEnvironment.ContentRootPath)            .AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true);          foreach (var buildAction in _configureAppConfigActions)          {          				buildAction(_hostBuilderContext, configBuilder);          }  				//构建数据源配置类          _appConfiguration = configBuilder.Build();          _hostBuilderContext.Configuration = _appConfiguration;}

在ConfigurationBuilder的Builder方法中初始化数据源有以下几个步骤:

1:遍历应用程序中添加的所有数据源,执行其HostBuilder的Build方法创建相对应的ConfigurationProvider

2:创建ConfigurationRoot对象,将第1步创建的ConfigurationProvider集合通过构造传参的方式传入ConfigurationRoot对象中,在ConfigurationRoot的构造函数中对遍历该集合,并依次对ConfigurationProvider执行Load()方法进行初始化数据源操作。

ConfigurationBuilder的Builder方法:

//Microsoft.Extensions.Configuration.ConfigurationBuilder.cspublic IConfigurationRoot Build(){		var providers = new List<IConfigurationProvider>();    //遍历所有的数据源		foreach (IConfigurationSource source in Sources)		{        //创建IConfigurationProvider实例				IConfigurationProvider provider = source.Build(this);				providers.Add(provider);		}    //创建ConfigurationRoot对象		return new ConfigurationRoot(providers);}

ConfigurationRoot对象同时包含了根据key获取和设置值的方法,该类为最终注入到依赖注入容器的类,并通过Get方法获取初始化后的值 。

//Microsoft.Extensions.Configuration.Configuration.cspublic class ConfigurationRoot : IConfigurationRoot, IDisposable{				private readonly IList<IConfigurationProvider> _providers;				private readonly IList<IDisposable> _changeTokenRegistrations;				private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();				public ConfigurationRoot(IList<IConfigurationProvider> providers)				{								if (providers == null)								{										throw new ArgumentNullException(nameof(providers));								}                _providers = providers;                _changeTokenRegistrations = new List<IDisposable>(providers.Count);                //遍历所有的IConfigurationProvider实例                foreach (IConfigurationProvider p in providers)                {                        //重点!执行初始化方法进行配置参数的初始化                        p.Load();                        _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));                }				}        public IEnumerable<IConfigurationProvider> Providers => _providers;        //索引器模式获取值         public string this[string key]        {                get                {                         //根据key获取值时,倒序遍历所有的ConfigurationProvider,取到第一个值就返回                         //注意:由于添加数据源顺序的不同,有可能会造成变量被覆盖的情况。                        for (int i = _providers.Count - 1; i >= 0; i--)                        {                                IConfigurationProvider provider = _providers[i];                                if (provider.TryGet(key, out string value))                                {                                				return value;                                }                        }                        return null;                }                set                {                        if (!_providers.Any())                        {                        				throw new InvalidOperationException(SR.Error_NoSources);                        }                        //对key所代表的value设置值。                        //遍历每一个IConfigurationprovider实例,并赋值。                        foreach (IConfigurationProvider provider in _providers)                        {                       				 provider.Set(key, value);                        }                }        }        public IEnumerable<IConfigurationSection> GetChildren() => this.GetChildrenImplementation(null);        public IChangeToken GetReloadToken() => _changeToken;        public IConfigurationSection GetSection(string key)=> new ConfigurationSection(this, key);        .....}

我们来看下EnvironmentVariablesProvider类:

 //Microsoft.Extensions.Configuration.EnvironmentVariables.EnvironmentVariablesConfigurationProvider.cspublic class EnvironmentVariablesConfigurationProvider : ConfigurationProvider{        ......        private readonly string _prefix;        public EnvironmentVariablesConfigurationProvider() : this(string.Empty)        { }        public EnvironmentVariablesConfigurationProvider(string prefix)        {        				_prefix = prefix ?? string.Empty;        }               public override void Load()        {       				 Load(Environment.GetEnvironmentVariables());        }        internal void Load(IDictionary envVariables)        {                var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);                IEnumerable<DictionaryEntry> filteredEnvVariables = envVariables                    .Cast<DictionaryEntry>()                    .SelectMany(AzureEnvToAppEnv)                    .Where(entry => ((string)entry.Key).StartsWith(_prefix, StringComparison.OrdinalIgnoreCase));                foreach (DictionaryEntry envVariable in filteredEnvVariables)                {                    string key = ((string)envVariable.Key).Substring(_prefix.Length);                    data[key] = (string)envVariable.Value;                }                Data = data;        }        ......}

EnvironmentConfigurationProvider类在Load方法的作用:

对当前机器的所有环境变量进行遍历,并根据传入的”DOTNET_”前缀来筛选出符合条件的环境变量。将符合条件的环境变量加入父类的Dictionary<string,string>字典中保存起来。

至此,环境变量的初始化工作完成。

初始化完成后,HostBuilder的Build方法在ConfigurationBuilder执行完后Build方法后,得到的IConfiguration类的实例,并在创建依赖注入容器的时候,将该ConfigurationRoot类以单例的形式注入到了依赖注入容器中,后续便可以通过依赖注入的形式来访问配置的变量了。

//Microsoft.Extensions.Hosting.HostBuilder.csprivate void BuildAppConfiguration(){          IConfigurationBuilder configBuilder = new ConfigurationBuilder()            .SetBasePath(_hostingEnvironment.ContentRootPath)            .AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true);          ......          _appConfiguration = configBuilder.Build();//构造IConfiguration配置实例          ......} private void CreateServiceProvider() {   			......        services.AddSingleton(_ => _appConfiguration);//将IConfiguration实例注入至依赖注入容器中   			...... }

至此,ConfigurationProvider模式介绍完成。

使用方式:在相关类上通过构造传入IConfiguration接口,依赖注入容器会解析出(IConfiguration)ConfigurationRoot对象,通过ConfigurationRoot的索引器方法即可查询到相关的值。

标签: #net core 配置