• .NET Core 3.0之深入源码理解Configuration(三)


     

    写在前面

    上一篇文章讨论了文件型配置的基本内容,本篇内容讨论JSON型配置的实现方式,理解了这一种配置类型的实现方式,那么其他类型的配置实现方式基本可以触类旁通。看过了上一篇文章的朋友,应该看得出来似曾相识。此图主要表达了文件型配置的实现,当然其他配置,包括自定义配置,都会按照这样的方式去实现。

    绘图3

    JSON配置组件的相关内容

    该组件有四个类

    • JsonConfigurationExtensions
    • JsonConfigurationSource
    • JsonConfigurationFileParser
    • JsonConfigurationProvider

    这四个类相互合作,职责明确,共同将JSON类型的配置加载到内存中,供相应的系统使用。

    JsonConfigurationFileParser

    该类是一个内部类,拥有一个私有的构造方法,意味着该类无法在其他地方进行实例化,只能在自己内部使用。它只有一个公共方法,并且是静态的,如下所示

       1:  public static IDictionary<string, string> Parse(Stream input) => new JsonConfigurationFileParser().ParseStream(input);

    该方法通过读取输入数据流,将其转化为字典类型的配置数据,该字典类型是SortedDictionary类型的,并且不区分大小写,此处需要注意。这也呼应了之前所说的.NET Core Configuration对外使用的时候,都是以字典方式去提供给外界使用的。

    那么,这个类是如何将数据流转化为JSON的呢,我们继续阅读源码

       1:  private IDictionary<string, string> ParseStream(Stream input)
       2:  {
       3:      _data.Clear();
       4:   
       5:      using (var reader = new StreamReader(input))
       6:      using (JsonDocument doc = JsonDocument.Parse(reader.ReadToEnd(), new JsonReaderOptions { CommentHandling = JsonCommentHandling.Skip }))
       7:      {
       8:          if (doc.RootElement.Type != JsonValueType.Object)
       9:          {
      10:              throw new FormatException(Resources.FormatError_UnsupportedJSONToken(doc.RootElement.Type));
      11:          }
      12:          VisitElement(doc.RootElement);
      13:      }
      14:   
      15:      return _data;
      16:  }

    通过源码,我们知道,此处使用了JsonDocument处理StreamReader数据,JsonDocument又是什么呢,通过命名空间我们知道,它位于System.Text.Json中,这是.NET Core原生的处理JSON的组件,有兴趣的朋友可以去翻翻MSDN或者GitHub查找相关资料。此处不做过多说明。

    VisitElement方法主要是遍历JsonElement.EnumerateObject()方法中的对象集合,此处采用Stack<string>实例进行数据安全方面的控制。其中VisitValue是一个在处理json时相当全面的方法,说它全面是因为它考虑到了JSON值的几乎所有类型:

    • JsonValueType.Object
    • JsonValueType.Array
    • JsonValueType.Number
    • JsonValueType.String
    • JsonValueType.True
    • JsonValueType.False
    • JsonValueType.Null

    当然,该方法,并不会很傻的处理每一种类型,主要是针对Object和Array类型进行了递归遍历,以便在诸如Number、String等的简单类型时跳出递归,并存放到字典中,需要再次强调的是,存放在字典中的值是以String类型存储的。

    至此,JsonConfigurationFileParser完成了从文件读取内容并转化为键值对的工作。

    JsonConfigurationSource

    这个类比较简单,因为继承自FileConfigurationSource,如前文所说,FileConfigurationSource类已经做了初步的实现,只提供了一个Build方法交给子类去重写。其返回值是JsonConfigurationProvider实例。

       1:  /// <summary>
       2:  /// Represents a JSON file as an <see cref="IConfigurationSource"/>.
       3:  /// </summary>
       4:  public class JsonConfigurationSource : FileConfigurationSource
       5:  {
       6:      /// <summary>
       7:      /// Builds the <see cref="JsonConfigurationProvider"/> for this source.
       8:      /// </summary>
       9:      /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
      10:      /// <returns>A <see cref="JsonConfigurationProvider"/></returns>
      11:      public override IConfigurationProvider Build(IConfigurationBuilder builder)
      12:      {
      13:          EnsureDefaults(builder);
      14:          return new JsonConfigurationProvider(this);
      15:      }
      16:  }

    此处的EnsureDefaults()方法,主要是设置FileProvider实例以及指定加载类型的异常处理方式。

    JsonConfigurationProvider

    这个类也很简单,它继承于FileConfigurationProvider,FileConfigurationProvider本身也已经通用功能进行了抽象实现,先看一下这个类的源码

       1:  /// <summary>
       2:  /// A JSON file based <see cref="FileConfigurationProvider"/>.
       3:  /// </summary>
       4:  public class JsonConfigurationProvider : FileConfigurationProvider
       5:  {
       6:      /// <summary>
       7:      /// Initializes a new instance with the specified source.
       8:      /// </summary>
       9:      /// <param name="source">The source settings.</param>
      10:      public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }
      11:   
      12:      /// <summary>
      13:      /// Loads the JSON data from a stream.
      14:      /// </summary>
      15:      /// <param name="stream">The stream to read.</param>
      16:      public override void Load(Stream stream)
      17:      {
      18:          try {
      19:              Data = JsonConfigurationFileParser.Parse(stream);
      20:          } catch (JsonReaderException e)
      21:          {
      22:              throw new FormatException(Resources.Error_JSONParseError, e);
      23:          }
      24:      }
      25:  }

    其构造函数的传入参数类型是JsonConfigurationSource,这和JsonConfigurationSource.Build()方法中的return new JsonConfigurationProvider(this)代码片段相呼应。

    JsonConfigurationProvider所重写的方法,调用的是JsonConfigurationFileParser.Parse(stream)方法,所以该类显得非常的轻量。

    JsonConfigurationExtensions

    这个方法,大家就更熟悉了,我们平时所使用的AddJsonFile()方法,就是在这个扩展类中进行扩展的,其返回值是IConfigurationBuilder类型,其核心方法源码如下所示

       1:  /// <summary>
       2:  /// Adds a JSON configuration source to <paramref name="builder"/>.
       3:  /// </summary>
       4:  /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
       5:  /// <param name="provider">The <see cref="IFileProvider"/> to use to access the file.</param>
       6:  /// <param name="path">Path relative to the base path stored in 
       7:  /// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param>
       8:  /// <param name="optional">Whether the file is optional.</param>
       9:  /// <param name="reloadOnChange">Whether the configuration should be reloaded if the file changes.</param>
      10:  /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
      11:  public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange)
      12:  {
      13:      if (builder == null)
      14:      {
      15:          throw new ArgumentNullException(nameof(builder));
      16:      }
      17:      if (string.IsNullOrEmpty(path))
      18:      {
      19:          throw new ArgumentException(Resources.Error_InvalidFilePath, nameof(path));
      20:      }
      21:   
      22:      return builder.AddJsonFile(s =>
      23:      {
      24:          s.FileProvider = provider;
      25:          s.Path = path;
      26:          s.Optional = optional;
      27:          s.ReloadOnChange = reloadOnChange;
      28:          s.ResolveFileProvider();
      29:      });
      30:  }

    不过,大家不要看这个方法的代码行数很多,就认为,其他方法都重载于该方法,其实该方法重载自

       1:  /// <summary>
       2:  /// Adds a JSON configuration source to <paramref name="builder"/>.
       3:  /// </summary>
       4:  /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
       5:  /// <param name="configureSource">Configures the source.</param>
       6:  /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
       7:  public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource)
       8:      => builder.Add(configureSource);

    这个方法最终调用的还是IConfigurationBuilder.Add()方法

    总结

    通过介绍以上JSON Configuration组件的四个类,我们知道了,该组件针对JSON格式的文件的处理方式,不过由于其实文件型配置,其抽象实现已经在文件型配置扩展实现。

    从这里,我们可以学习一下,如果有一天我们需要扩展远程配置,比如Consul、ZK等,我们也可以考虑并采用这种架构的设计方式。另外在JSON Configuration组件中,.NET Core将专有型功能方法的处理进行了聚合,并聚焦关注点的方式也值得我们学习。

    最后JsonConfigurationFileParser中给了我们一种关于Stream转换成JSON的实现,我们完全可以把这个类当成工具类去使用。

  • 相关阅读:
    PEP8 Python 编码规范整理
    github操作
    重零开始,写一个股票交易项目(1)
    矢量地图质量检查现状与需求(2篇)
    导航数据质量评价相关
    测绘数据国内外现状概述
    机器的反叛-机器的智能会超越人类吗?
    标签要素调用CSS样式优先级说明
    内存碎片概念及主要避免方式
    关于年终述职总结
  • 原文地址:https://www.cnblogs.com/edison0621/p/10891365.html
Copyright © 2020-2023  润新知