欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-05-21 20:19:18
文件型配置基本内容 上一篇文章讨论了Configuration的几个核心对象,本文继续讨论Configuration中关于文件型配置的相关内容。相比较而言,文件型配置的使用场景更加广泛,用户自定义配置扩展也可以基于文件型配置进行扩展。如果需要查看上一篇文章,可以点击移步。 .NET Core文件型配 ......
 

文件型配置基本内容

上一篇文章讨论了configuration的几个核心对象,本文继续讨论configuration中关于文件型配置的相关内容。相比较而言,文件型配置的使用场景更加广泛,用户自定义配置扩展也可以基于文件型配置进行扩展。如果需要查看上一篇文章,可以点击

.net core文件型配置中我们提供了三种主要的实现,分别是json、xml、ini,请查看下图

由图可知,这三种配置的实现方式是一样的,当然了其他的配置比如命令行配置、环境变量配置等也是大同小异,理解了改配置类型的实现方式,后面我们再扩展基于consul或者zk的实现,就非常简单了。

文件型配置的抽象扩展

文件型配置的抽象扩展位于microsoft.extensions.configuration.fileextensions组件中,该扩展是一个基础实现。不过其命名空间是microsoft.extensions.configuration,而micros oft.extensions.configuration扩建本身又是整个.net core configuration的基础实现。将file扩展独立于外部,体验了.net core的模块化设计。

fileconfigurationsource

configuration.fileextensions组件中,fileconfigurationsource是继承于iconfigurationsource的一个抽象类,包含了一个iconfigurationprovider类型的抽象方法,如下所示

   1:  /// <summary>
   2:  /// builds the <see cref="iconfigurationprovider"/> for this source.
   3:  /// </summary>
   4:  /// <param name="builder">the <see cref="iconfigurationbuilder"/>.</param>
   5:  /// <returns>a <see cref="iconfigurationprovider"/></returns>
   6:  public abstract iconfigurationprovider build(iconfigurationbuilder builder);

该抽象类中还包括了几个比较重要的参数,分别用于配置性行为、文件内容访问以及异常处理。

string path:文件的路径

bool optional:标识加载的文件是否是可选的

bool reloadonchange:如果文件发生修改,是否重新加载配置源

int reloaddelay:加载延迟,单位是毫秒,默认是250毫秒

ifileprovider fileprovider:用于获取文件内容

action<fileloadexceptioncontext> onloadexception:文件加载异常处理

该类对fileprovider有特殊处理,就是如果没有提供fileprovider实例,则会基于绝对路径,在最近的现有目录中创建物理文件提供程序。源码如下,

   1:  /// <summary>
   2:  /// if no file provider has been set, for absolute path, this will creates a physical file provider 
   3:  /// for the nearest existing directory.
   4:  /// </summary>
   5:  public void resolvefileprovider()
   6:  {
   7:      if (fileprovider == null && 
   8:          !string.isnullorempty(path) &&
   9:          system.io.path.ispathrooted(path))
  10:      {
  11:      
var directory = system.io.path.getdirectoryname(path);
  12:          var pathtofile = system.io.path.getfilename(path);
  13:          while (!string.isnullorempty(directory) && !directory.exists(directory))
  14:          {
  15:              pathtofile = system.io.path.combine(system.io.path.getfilename(directory), pathtofile);
  16:              directory = system.io.path.getdirectoryname(directory);
  17:          }
  18:          if (directory.exists(directory))
  19:          {
  20:              fileprovider = new physicalfileprovider(directory);
  21:              path = pathtofile;
  22:          }
  23:      }
  24:  }

fileconfigurationprovider

该类是继承于configurationprovider的抽象类,是从文件系统加载配置的基类,同时还继承了idisposable,其抽象方法是load方法,用于从当前的provider中以stream方式加载数据

   1:  /// <summary>
   2:  /// loads this provider's data from a stream.
   3:  /// </summary>
   4:  /// <param name="stream">the stream to read.</param>
   5:  public abstract void load(stream stream);

该类还重写了configurationprovider的load方法,并对文件加载中的异常做了处理,data属性在前文有提到过,此处不再做其他说明。方法源码如下所示:

   1:  private void load(bool reload)
   2:  {
   3:      var file = source.fileprovider?.getfileinfo(source.path);
   4:      if (file == null || !file.exists)
   5:      {
   6:          if (source.optional || reload) // always optional on reload
   7:          {
   8:              data = new dictionary<string, string>(stringcomparer.ordinalignorecase);
   9:          }
  10:          else
  11:          {
  12:              var error = new stringbuilder($"the configuration file '{source.path}' was not found and is not optional.");
  13:              if (!string.isnullorempty(file?.physicalpath))
  14:              {
  15:                  error.append($" the physical path is '{file.physicalpath}'.");
  16:              }
  17:              handleexception(new filenotfoundexception(error.tostring()));
  18:          }
  19:      }
  20:      else
  21:      {
  22:          // always create new data on reload to drop old keys
  23:          if (reload)
  24:          {
  25:              data = new dictionary<string, string>(stringcomparer.ordinalignorecase);
  26:          }
  27:          using (var stream = file.createreadstream())
  28:          {
  29:              try
  30:              {
  31:                  load(stream);
  32:              }
  33:              catch (exception e)
  34:              {
  35:                  handleexception(e);
  36:              }
  37:          }
  38:      }
  39:      // review: should we raise this in the base as well / instead?,通过注释,我们可以知道onreload()方法可能会在新版中发生变化
  40:      onreload();
  41:  }
  42:   
  43:  /// <summary>
  44:  /// loads the contents of the file at <see cref="path"/>.
  45:  /// </summary>
  46:  /// <exception cref="filenotfoundexception">if optional is <c>false</c> on the source and a
  47:  /// file does not exist at specified path.</exception>
  48:  public override void load()
  49:  {
  50:      load(reload: false);
  51:  }

另外它还有一个特殊方法,就是参数类型为fileconfigurationsource的构造函数,其主要功能是监控文件,并在fileconfigurationsource.reloaddelay设置的时间里重新加载文件并返回一个idisposable类型的值,以下是该构造函数的源码:

   1:  /// <summary>
   2:  /// initializes a new instance with the specified source.
   3:  /// </summary>
   4:  /// <param name="source">the source settings.</param>
   5:  public fileconfigurationprovider(fileconfigurationsource source)
   6:  {
   7:      if (source == null)
   8:      {
   9:          throw new argumentnullexception(nameof(source));
  10:      }
  11:      source = source;
  12:   
  13:      if (source.reloadonchange && source.fileprovider != null)
  14:      {
  15:          _changetokenregistration = changetoken.onchange(
  16:              () => source.fileprovider.watch(source.path),
  17:              () => {
  18:                  thread.sleep(source.reloaddelay);
  19:                  load(reload: true);
  20:              });
  21:      }
  22:  }

fileconfigurationextensions

该类是一个静态类,其提供了的多个扩展方法,主要基于

  • iconfigurationbuilder
  • ifileprovider
  • action<fileloadexceptioncontext>

包括主要用于设置或获取ifileprovider对象,前文有介绍过,是存储于字典之中,需要注意的是,在get的时候如果字典中并不存在ifileprovider对象,则会实例化一个physicalfileprovider对象出来,该类位于microsoft.extensions.fileproviders.physicalfileprovider

   1:  /// <summary>
   2:  /// sets the default <see cref="ifileprovider"/> to be used for file-based providers.
   3:  /// </summary>
   4:  /// <param name="builder">the <see cref="iconfigurationbuilder"/> to add to.</param>
   5:  /// <param name="fileprovider">the default file provider instance.</param>
   6:  /// <returns>the <see cref="iconfigurationbuilder"/>.</returns>
   7:  public static iconfigurationbuilder setfileprovider(this iconfigurationbuilder builder, ifileprovider fileprovider)
   8:  {
   9:      if (builder == null)
  10:      {
  11:          throw new argumentnullexception(nameof(builder));
  12:      }
  13:   
  14:      builder.properties[fileproviderkey] = fileprovider ?? throw new argumentnullexception(nameof(fileprovider));
  15:      return builder;
  16:  }
  17:   
  18:  /// <summary>
  19:  /// gets the default <see cref="ifileprovider"/> to be used for file-based providers.
  20:  /// </summary>
  21:  /// <param name="builder">the <see cref="iconfigurationbuilder"/>.</param>
  22:  /// <returns>the <see cref="iconfigurationbuilder"/>.</returns>
  23:  public static ifileprovider getfileprovider(this iconfigurationbuilder builder)
  24:  {
  25:      if (builder == null)
  26:      {
  27:          throw new argumentnullexception(nameof(builder));
  28:      }
  29:   
  30:      if (builder.properties.trygetvalue(fileproviderkey, out object provider))
  31:      {
  32:          return provider as ifileprovider;
  33:      }
  34:   
  35:      return new physicalfileprovider(appcontext.basedirectory ?? string.empty);
  36:  }

为指定路径的物理文件设置文件型provider,该方法同样基于physicalfileprovider,并返回iconfigurationbuilder对象

   1:  /// <summary>
   2:  /// sets the fileprovider for file-based providers to a physicalfileprovider with the base path.
   3:  /// </summary>
   4:  /// <param name="builder">the <see cref="iconfigurationbuilder"/> to add to.</param>
   5:  /// <param name="basepath">the absolute path of file-based providers.</param>
   6:  /// <returns>the <see cref="iconfigurationbuilder"/>.</returns>
   7:  public static iconfigurationbuilder setbasepath(this iconfigurationbuilder builder, string basepath)
   8:  {
   9:      if (builder == null)
  10:      {
  11:          throw new argumentnullexception(nameof(builder));
  12:      }
  13:   
  14:      if (basepath == null)
  15:      {
  16:          throw new argumentnullexception(nameof(basepath));
  17:      }
  18:   
  19:      return builder.setfileprovider(new physicalfileprovider(basepath));
  20:  }

以及异常处理,可以看到其异常处理也会存放于字典中,如果字典中找不到,就会返回空,这个地方如果直接使用,需要注意空指针问题。

   1:  /// <summary>
   2:  /// sets a default action to be invoked for file-based providers when an error occurs.
   3:  /// </summary>
   4:  /// <param name="builder">the <see cref="iconfigurationbuilder"/> to add to.</param>
   5:  /// <param name="handler">the action to be invoked on a file load exception.</param>
   6:  /// <returns>the <see cref="iconfigurationbuilder"/>.</returns>
   7:  public static iconfigurationbuilder setfileloadexceptionhandler(this iconfigurationbuilder builder, action<fileloadexceptioncontext> handler)
   8:  {
   9:      if (builder == null)
  10:      {
  11:          throw new argumentnullexception(nameof(builder));
  12:      }
  13:   
  14:      builder.properties[fileloadexceptionhandlerkey] = handler;
  15:      return builder;
  16:  }
  17:   
  18:  /// <summary>
  19:  /// gets the default <see cref="ifileprovider"/> to be used for file-based providers.
  20:  /// </summary>
  21:  /// <param name="builder">the <see cref="iconfigurationbuilder"/>.</param>
  22:  /// <returns>the <see cref="iconfigurationbuilder"/>.</returns>
  23:  public static action<fileloadexceptioncontext> getfileloadexceptionhandler(this iconfigurationbuilder builder)
  24:  {
  25:      if (builder == null)
  26:      {
  27:          throw new argumentnullexception(nameof(builder));
  28:      }
  29:   
  30:      if (builder.properties.trygetvalue(fileloadexceptionhandlerkey, out object handler))
  31:      {
  32:          return handler as action<fileloadexceptioncontext>;
  33:      }
  34:      
return null;
  35:  }

该类还有两个静态私有变量,指定了文件provider的key以及文件加载异常处理key。

   1:  private static string fileproviderkey = "fileprovider";
   2:  private static string fileloadexceptionhandlerkey = "fileloadexceptionhandler";

总结

文件型配置还依赖于.net core的其他组件microsoft.extensions.fileproviders和microsoft.extensions.primitives。

fileproviders组件提供了文件处理的一般方法,primitives组件提供了监控机制,同时还包括两个比较重要的结构体stringvalues和stringsegment,本文暂时不做讨论,有兴趣的朋友,可以自行查看该组件源码。