概述
我们都知道asp.net core配置信息的读取离不开IConfigurationSource和IConfigurationProvider这两个类,ConfigurationSource可以提供一个ConfigurationProvider,然后去读取信息。究竟他们之间有着怎样的千丝万缕,我们一起来看看源码。
首先我们来建立一个.net core控制台项目,来运行以下代码:
class Program { static void Main(string[] args) { ConfigurationBuilder configBuilder = new ConfigurationBuilder(); configBuilder.SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); var configFile = configBuilder.Build(); Console.ReadKey(); } }
短短几行 代码看起来很简单,就是用来读取appsettings.json文件中的配置信息。然而我们今天想搞清楚其背后运行的原理,要花点时间。
首先、我们根据代码ConfigurationBuilder configBuilder = new ConfigurationBuilder();知道创建了一个configBuilder对象;
其次,configBuilder.SetBasePath(Directory.GetCurrentDirectory()) 该代码的调用我们也能大概见名知义,获取当前的目录;
接下来,重点来了,configBuilder.AddJsonFile("appsettings.json")的实现究竟是怎样的?我们来看下源码的实现:
f12进去后源码如下:
///Extension methods for adding . public static class JsonConfigurationExtensions { /// Adds the JSON configuration provider at to . /// The to add to. /// Path relative to the base path stored in /// of . /// The . public static IConfigurationBuilder AddJsonFile( this IConfigurationBuilder builder, string path) { return builder.AddJsonFile((IFileProvider) null, path, false, false); } }
紧接着f12再看实现的源码,依然在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(SR.Error_InvalidFilePath, nameof (path)); return builder.AddJsonFile((Action) (s => { s.FileProvider = provider; s.Path = path; s.Optional = optional; s.ReloadOnChange = reloadOnChange; s.ResolveFileProvider(); })); }
这时候有没有发现builder.AddJsonFile((Action
看完上面这个关系图后,我们紧接着上面builder.AddJsonFile()的实现源码继续f12往下,源码如下:
//该代码依然在JsonConfigurationExtensions类里面
public static IConfigurationBuilder AddJsonFile( this IConfigurationBuilder builder, ActionconfigureSource) { return ConfigurationExtensions.Add (builder, (Action ) configureSource); }
我们看到上面的扩展方法实现是ConfigurationExtensions.Add...,再往下看实现:
public static class ConfigurationExtensions { ///Adds a new configuration source. /// The to add to. /// Configures the source secrets. /// /// The . public static IConfigurationBuilder Add ( this IConfigurationBuilder builder, Action configureSource) where TSource : IConfigurationSource, new() { TSource source = new TSource(); if (configureSource != null) configureSource(source); return builder.Add((IConfigurationSource) source); } }
到这里我们看到了其实就是IConfigurationBuilder调用了Add方法,添加了一个数据源(JsonConfigurationSource),至于JsonConfigurationSource类里面做了什么,我们看下实现。
public class JsonConfigurationSource : FileConfigurationSource { ///Builds the for this source. /// The . /// A public override IConfigurationProvider Build(IConfigurationBuilder builder) { this.EnsureDefaults(builder); return (IConfigurationProvider) new JsonConfigurationProvider(this); } }
JsonConfigurationSource类面的Build方法提供了一个JsonConfigurationProvider类,这里再贴下JsonConfigurationProvider类里面的代码:
///A JSON file based . public class JsonConfigurationProvider : FileConfigurationProvider { /// Initializes a new instance with the specified source. /// The source settings. public JsonConfigurationProvider(JsonConfigurationSource source) : base((FileConfigurationSource) source) { } /// Loads the JSON data from a stream. /// The stream to read. public virtual void Load(Stream stream) { try { this.set_Data(JsonConfigurationFileParser.Parse(stream)); } catch (JsonException ex) { throw new FormatException(SR.Error_JSONParseError, (Exception) ex); } } }
关于JsonConfigurationProvider里面的Load就是去读取信息的实现,至于Load的具体实现我们不再深究。我们回到最初的控制台configBuilder.Build(),看看其的实现:
public class ConfigurationBuilder : IConfigurationBuilder { ///Returns the sources used to obtain configuration values. public IList Sources { get; } = (IList ) new List (); /// Gets a key/value collection that can be used to share data between the /// and the registered s. public IDictionary<string, object> Properties { get; } = (IDictionary<string, object>) new Dictionary<string, object>(); /// Adds a new configuration source. /// The configuration source to add. /// The same . public IConfigurationBuilder Add(IConfigurationSource source) { if (source == null) throw new ArgumentNullException(nameof (source)); this.Sources.Add(source); return (IConfigurationBuilder) this; } /// Builds an with keys and values from the set of providers registered in /// . /// An with keys and values from the registered providers. public IConfigurationRoot Build() { List configurationProviderList = new List (); foreach (IConfigurationSource source in (IEnumerable ) this.Sources) { IConfigurationProvider configurationProvider = source.Build((IConfigurationBuilder) this); configurationProviderList.Add(configurationProvider); } return (IConfigurationRoot) new ConfigurationRoot((IList ) configurationProviderList); } }
看到这个源码的时候有没有种豁然开朗的感觉,前面我们说到IConfigurationBuilder调用了Add方法添加一个数据源,并没说添加了一个数据源存在了哪里,到底有什么用处,现在在上面ConfigurationBuilder类里面看到存在了Sources 集合里面。然后configBuilder.Build()
去调用的时候遍历数据源(Sources )集合,紧接着source (IConfigurationSource)调用了Build方法构建了一个configurationProvider对象存到configurationProviderList集合里面,最后在返回一个ConfigurationRoot对象的构造函数里面传递了configurationProviderList集合去执行。
贴上ConfigurationRoot的源码:
public class ConfigurationRoot : IConfigurationRoot, IConfiguration, IDisposable { private readonly IList_providers; private readonly IList _changeTokenRegistrations; /// Initializes a Configuration root with a list of providers. /// The s for this configuration. public ConfigurationRoot(IList providers) { if (providers == null) throw new ArgumentNullException(nameof (providers)); this._providers = providers; this._changeTokenRegistrations = (IList ) new List (providers.Count); foreach (IConfigurationProvider provider in (IEnumerable ) providers) { IConfigurationProvider p = provider; p.Load(); this._changeTokenRegistrations.Add(ChangeToken.OnChange((Func ) (() => p.GetReloadToken()), (Action) (() => this.RaiseChanged()))); } } }
看到没,最后providers去调用了load方法。
结语
就上面的控制台代码来说IConfigurationSource对应的实现是JsonConfigurationSource;IConfigurationProvider,抽象类ConfigurationProvider对应的实现为JsonConfigurationProvider。如果我们要换成别的文件格式呢?比如ini,怎样自定义配置源呢?大家可以先想想,其实也很简单,下次跟大家分享。
最后说真的,.netCore源码真的特别优秀,很值得花一番时间去看看!从其中可以学到许多架构知识!