7

关于Asp.net core配置信息读取的源码分析梳理

 2 years ago
source link: https://www.cnblogs.com/mhg215/p/15514184.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

关于Asp.net core配置信息读取的源码分析梳理

我们都知道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进去后源码如下:

/// <summary>Extension methods for adding <see cref="T:Microsoft.Extensions.Configuration.Json.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="T:Microsoft.Extensions.Configuration.IConfigurationBuilder" /> to add to.</param>
    /// <param name="path">Path relative to the base path stored in
    /// <see cref="P:Microsoft.Extensions.Configuration.IConfigurationBuilder.Properties" /> of <paramref name="builder" />.</param>
    /// <returns>The <see cref="T:Microsoft.Extensions.Configuration.IConfigurationBuilder" />.</returns>
    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<JsonConfigurationSource>) (s =>
      {
        s.FileProvider = provider;
        s.Path = path;
        s.Optional = optional;
        s.ReloadOnChange = reloadOnChange;
        s.ResolveFileProvider();
      }));
    }

这时候有没有发现builder.AddJsonFile((Action<JsonConfigurationSource>)这个方法里面出现了一个关键的信息点:JsonConfigurationSource (JsonConfigurationSource 继承抽象类FileConfigurationSource,而FileConfigurationSource:IConfigurationSource) 。 关系如下图:

 看完上面这个关系图后,我们紧接着上面builder.AddJsonFile()的实现源码继续f12往下,源码如下:

//该代码依然在JsonConfigurationExtensions类里面
public static IConfigurationBuilder AddJsonFile( this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource) { return ConfigurationExtensions.Add<JsonConfigurationSource>(builder, (Action<M0>) configureSource); }

我们看到上面的扩展方法实现是ConfigurationExtensions.Add...,再往下看实现:

public static class ConfigurationExtensions
  {
    /// <summary>Adds a new configuration source.</summary>
    /// <param name="builder">The <see cref="T:Microsoft.Extensions.Configuration.IConfigurationBuilder" /> to add to.</param>
    /// <param name="configureSource">Configures the source secrets.</param>
    /// <typeparam name="TSource" />
    /// <returns>The <see cref="T:Microsoft.Extensions.Configuration.IConfigurationBuilder" />.</returns>
    public static IConfigurationBuilder Add<TSource>(
      this IConfigurationBuilder builder,
      Action<TSource> 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
  {
    /// <summary>Builds the <see cref="T:Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider" /> for this source.</summary>
    /// <param name="builder">The <see cref="T:Microsoft.Extensions.Configuration.IConfigurationBuilder" />.</param>
    /// <returns>A <see cref="T:Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider" /></returns>
    public override IConfigurationProvider Build(IConfigurationBuilder builder)
    {
      this.EnsureDefaults(builder);
      return (IConfigurationProvider) new JsonConfigurationProvider(this);
    }
  }

JsonConfigurationSource类面的Build方法提供了一个JsonConfigurationProvider类,这里再贴下JsonConfigurationProvider类里面的代码:

  /// <summary>A JSON file based <see cref="T:Microsoft.Extensions.Configuration.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((FileConfigurationSource) source)
    {
    }

    /// <summary>Loads the JSON data from a stream.</summary>
    /// <param name="stream">The stream to read.</param>
    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
  {
    /// <summary>Returns the sources used to obtain configuration values.</summary>
    public IList<IConfigurationSource> Sources { get; } = (IList<IConfigurationSource>) new List<IConfigurationSource>();

    /// <summary>Gets a key/value collection that can be used to share data between the <see cref="T:Microsoft.Extensions.Configuration.IConfigurationBuilder" />
    /// and the registered <see cref="T:Microsoft.Extensions.Configuration.IConfigurationProvider" />s.</summary>
    public IDictionary<string, object> Properties { get; } = (IDictionary<string, object>) 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="T:Microsoft.Extensions.Configuration.IConfigurationBuilder" />.</returns>
    public IConfigurationBuilder Add(IConfigurationSource source)
    {
      if (source == null)
        throw new ArgumentNullException(nameof (source));
      this.Sources.Add(source);
      return (IConfigurationBuilder) this;
    }

    /// <summary>Builds an <see cref="T:Microsoft.Extensions.Configuration.IConfiguration" /> with keys and values from the set of providers registered in
    /// <see cref="P:Microsoft.Extensions.Configuration.ConfigurationBuilder.Sources" />.</summary>
    /// <returns>An <see cref="T:Microsoft.Extensions.Configuration.IConfigurationRoot" /> with keys and values from the registered providers.</returns>
    public IConfigurationRoot Build()
    {
      List<IConfigurationProvider> configurationProviderList = new List<IConfigurationProvider>();
      foreach (IConfigurationSource source in (IEnumerable<IConfigurationSource>) this.Sources)
      {
        IConfigurationProvider configurationProvider = source.Build((IConfigurationBuilder) this);
        configurationProviderList.Add(configurationProvider);
      }
      return (IConfigurationRoot) new ConfigurationRoot((IList<IConfigurationProvider>) 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<IConfigurationProvider> _providers;
    private readonly IList<IDisposable> _changeTokenRegistrations;

    /// <summary>Initializes a Configuration root with a list of providers.</summary>
    /// <param name="providers">The <see cref="T:Microsoft.Extensions.Configuration.IConfigurationProvider" />s for this configuration.</param>
    public ConfigurationRoot(IList<IConfigurationProvider> providers)
    {
      if (providers == null)
        throw new ArgumentNullException(nameof (providers));
      this._providers = providers;
      this._changeTokenRegistrations = (IList<IDisposable>) new List<IDisposable>(providers.Count);
      foreach (IConfigurationProvider provider in (IEnumerable<IConfigurationProvider>) providers)
      {
        IConfigurationProvider p = provider;
        p.Load();
        this._changeTokenRegistrations.Add(ChangeToken.OnChange((Func<IChangeToken>) (() => p.GetReloadToken()), (Action) (() => this.RaiseChanged())));
      }
    }
}

看到没,最后providers去调用了load方法。

就上面的控制台代码来说IConfigurationSource对应的实现是JsonConfigurationSource;IConfigurationProvider,抽象类ConfigurationProvider对应的实现为JsonConfigurationProvider。如果我们要换成别的文件格式呢?比如ini,怎样自定义配置源呢?大家可以先想想,其实也很简单,下次跟大家分享。

最后说真的,.netCore源码真的特别优秀,很值得花一番时间去看看!从其中可以学到许多架构知识!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK