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

原文:.NET Core 3.0之深入源码理解Configuration(一)

Configuration总体介绍

微软在.NET Core里设计出了全新的配置体系,并以非常灵活、可扩展的方式实现。从其源码来看,其运行机制大致是,根据其Source,创建一个Builder实例,并会向其添加Provider,在我们使用配置信息的时候,会从内存中获取相应的Provider实例。

.NET Core采用了统一的调用方式来加载不同类型的配置信息,并通过统一的抽象接口IConfigurationSource对配置源进行管理,这也是刚刚所说的灵活。而其扩展性就是我们可以自己自定义新的Provider实例,而不会改变其原来的调用方式。接下来的文章将会基于Consul,扩展一个新的Provider实例。

在ASP.NET Core 中,我们的应用配置是基于IConfigurationProvider的键值对。 我们先看一下思维导图:

基于上图,我们可以看到主要有键值对有多种,分别是:

环境变量

命令行参数

各种形式的配置文件

内存对象

用户自定义扩展源

核心对象

在介绍.NET Core配置功能之前,先简要说明一下Microsoft.Extensions.Configuration.Abstractions,该组件抽象了.NET Core的配置功能,并对自定义扩展制定了新的标准。以下介绍的四个核心对象全部来自于该组件。

IConfiguration

该接口表示一组键/值应用程序配置属性,应用程序使用配置时的入口对象,.NET Core对其有多种扩展,其派生类包括位于统一类库的IConfigurationSection,以及Microsoft.Extensions.Configuration类库中的ConfigurationRoot、ConfigurationSection、IConfigurationRoot。我们可以通过DI获取IConfiguration实例。

它主要有以下三个方法:

  • GetChildren():获取直接子配置子节
  • GetReloadToken():返回一个IChangeToken,可用于确定何时重新加载配置
  • GetSection(String):获取指定键的子节点

我们来看一下源码:

   1:  /// <summary>
   2:      /// Represents a set of key/value application configuration properties.
   3:      /// </summary>
   4:      public interface IConfiguration
   5:      {
   6:          /// <summary>
   7:          /// Gets or sets a configuration value.
   8:          /// </summary>
   9:          /// <param name="key">The configuration key.</param>
  10:          /// <returns>The configuration value.</returns>
  11:          string this[string key] { get; set; }
  12:   
  13:          /// <summary>
  14:          /// Gets a configuration sub-section with the specified key.
  15:          /// </summary>
  16:          /// <param name="key">The key of the configuration section.</param>
  17:          /// <returns>The <see cref="IConfigurationSection"/>.</returns>
  18:          /// <remarks>
  19:          ///     This method will never return <c>null</c>. If no matching sub-section is found with the specified key,
  20:          ///     an empty <see cref="IConfigurationSection"/> will be returned.
  21:          /// </remarks>
  22:          IConfigurationSection GetSection(string key);
  23:   
  24:          /// <summary>
  25:          /// Gets the immediate descendant configuration sub-sections.
  26:          /// </summary>
  27:          /// <returns>The configuration sub-sections.</returns>
  28:          IEnumerable<IConfigurationSection> GetChildren();
  29:   
  30:          /// <summary>
  31:          /// Returns a <see cref="IChangeToken"/> that can be used to observe when this configuration is reloaded.
  32:          /// </summary>
  33:          /// <returns>A <see cref="IChangeToken"/>.</returns>
  34:          IChangeToken GetReloadToken();
  35:      }

通常我们要求配置文件要有足够的灵活性,尤其是我们所扩展的配置信息存放在了其他服务器,当修改的时候我们很需要一套监控功能,以及时灵活的应对配置信息的修改。现在.NET Core为我们提供了这样一个功能,我们只需要自定义少量代码即可完成配置信息的同步。这个方法就是GetReloadToken(),其返回值是IChangeToken。此处对配置信息的同步只做一个引子,后面的文章会详细说明。

由于ConfigurationRoot、ConfigurationSection聚集于IConfiguration接口,此处也对这两个类进行讨论,方便我们对.NET Core的配置功能有个更加形象的印象。这两个接口,本质上就是.NET Core关于配置信息的读取方式。

XML是使用比较广泛的一种数据结构,我们在配置XML时,一般会使用根节点、父节点、子节点之类的术语,此处也一样。

ConfigurationRoot是配置的根节点,也实现了IConfigurationRoot,此接口只有一个方法,其主要功能就是实现对配置信息的重新加载,另外还包括一个IConfigurationProvider类型的集合属性。其源码如下

   1:  /// <summary>
   2:  /// Represents the root of an <see cref="IConfiguration"/> hierarchy.
   3:  /// </summary>
   4:  public interface IConfigurationRoot : IConfiguration
   5:  {
   6:      /// <summary>
   7:      /// Force the configuration values to be reloaded from the underlying <see cref="IConfigurationProvider"/>s.
   8:      /// </summary>
   9:      void Reload();
  10:   
  11:      /// <summary>
  12:      /// The <see cref="IConfigurationProvider"/>s for this configuration.
  13:      /// </summary>
  14:      IEnumerable<IConfigurationProvider> Providers { get; }
  15:  }

下面是ConfigurationRoot关于Reload()方法的实现

   1:  /// <summary>
   2:  /// Force the configuration values to be reloaded from the underlying sources.
   3:  /// </summary>
   4:  public void Reload()
   5:  {
   6:      foreach (var provider in _providers)
   7:      {
   8:          provider.Load();
   9:      }
  10:   
  11:      RaiseChanged();
  12:  }

通过源码我们知道,如果调用了Reload()方法,所有类型的Provider都会重新加载。

前面有ConfigurationRoot表示配置的根节点,那么ConfigurationSection则表示非跟节点,毕竟父节点、子节点都是相对,所以此处使用非根节点。ConfigurationSection继承于IConfigurationSection,该接口只有三个只读属性,分别表示配置信息的Key、Value以及路径信息,需要指出的是,此处的路径信息主要指从根节点到当前节点的路径,以表示当前节点的位置,类似于A:B:C可以表示节点C的位置,其中A、B、C都是ConfigurationSection的Key。以下是ConfigurationSection的源码

   1:  /// <summary>
   2:  /// Represents a section of application configuration values.
   3:  /// </summary>
   4:  public interface IConfigurationSection : IConfiguration
   5:  {
   6:      /// <summary>
   7:      /// Gets the key this section occupies in its parent.
   8:      /// </summary>
   9:      string Key { get; }
  10:   
  11:      /// <summary>
  12:      /// Gets the full path to this section within the <see cref="IConfiguration"/>.
  13:      /// </summary>
  14:      string Path { get; }
  15:   
  16:      /// <summary>
  17:      /// Gets or sets the section value.
  18:      /// </summary>
  19:      string Value { get; set; }
  20:  }

IConfigurationBuilder

该接口主要用于创建IConfigurationProvider,其派生类包括Microsoft.Extensions.Configuration.ConfigurationBuilder。其成员包括

两个只读属性:

  • Properties:获取可用于在IConfigurationBuilder之间共享数据的键/值集合
  • Sources:该属性用于缓存不同的配置源,以用于相对应的Provider的创建

两个方法:

  • Add(IConfigurationSource source):新增IConfigurationSource,并添加到属性中Sources中
  • Build():该方法遍历Sources属性,并调用IConfigurationSource的Build()方法,通过获取Provider集合,最终创建IConfigurationRoot对象

ConfigurationBuilder源码如下

   1:  /// <summary>
   2:      /// Used to build key/value based configuration settings for use in an application.
   3:      /// </summary>
   4:      public class ConfigurationBuilder : IConfigurationBuilder
   5:      {
   6:          /// <summary>
   7:          /// Returns the sources used to obtain configuration values.
   8:          /// </summary>
   9:          public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();
  10:   
  11:          /// <summary>
  12:          /// Gets a key/value collection that can be used to share data between the <see cref="IConfigurationBuilder"/>
  13:          /// and the registered <see cref="IConfigurationProvider"/>s.
  14:          /// </summary>
  15:          public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>();
  16:   
  17:          /// <summary>
  18:          /// Adds a new configuration source.
  19:          /// </summary>
  20:          /// <param name="source">The configuration source to add.</param>
  21:          /// <returns>The same <see cref="IConfigurationBuilder"/>.</returns>
  22:          public IConfigurationBuilder Add(IConfigurationSource source)
  23:          {
  24:              if (source == null)
  25:              {
  26:                  throw new ArgumentNullException(nameof(source));
  27:              }
  28:   
  29:              Sources.Add(source);
  30:              return this;
  31:          }
  32:   
  33:          /// <summary>
  34:          /// Builds an <see cref="IConfiguration"/> with keys and values from the set of providers registered in
  35:          /// <see cref="Sources"/>.
  36:          /// </summary>
  37:          /// <returns>An <see cref="IConfigurationRoot"/> with keys and values from the registered providers.</returns>
  38:          public IConfigurationRoot Build()
  39:          {
  40:              var providers = new List<IConfigurationProvider>();
  41:              foreach (var source in Sources)
  42:              {
  43:                  var provider = source.Build(this);
  44:                  providers.Add(provider);
  45:              }
  46:              return new ConfigurationRoot(providers);
  47:          }
  48:      }

此处令人感慨颇多,我们最终调用 ConfigurationRoot 的构造函数,究其原因是Provider提供了统一的数据访问方式,不管是基于何种类型的Provider,我们都可以调用其Load()方法加载配置项。此外,IConfigurationBuilder本身有很多的扩展方法来注册数据源,比如AddJsonFile()扩展方法。我们来看一下,我们常见的写法,

   1:  var builder = new ConfigurationBuilder()
   2:   
   3:              .SetBasePath(env.ContentRootPath)
   4:   
   5:              .AddJsonFile("appsettings1.json", false, true)
   6:   
   7:              .AddJsonFile("appsettings2.json", false, true);
   8:   
   9:  Configuration = builder.Build();

IConfigurationSource

该接口表示应用程序配置的键值对。其派生类包括Microsoft.Extensions.Configuration.ChainedConfigurationSource、Microsoft.Extensions.Configuration.Memory.MemoryConfigurationSource。另外该派生类还会在文件类配置场景下依赖Microsoft.Extensions.Configuration.FileExtensions组件。

它是所有配置源的抽象表示,包括JSON、XML、INI、环境变量等等。通过上文我们也知道了,IConfigurationBuilder会注册多个IConfigurationSource实例。它只有一个方法,就是Build()方法,并返回IConfigurationProvider,由此可见,IConfigurationProvider的创建依赖于IConfigurationSource,这也是一一对应的关系。所有不同的源最终都会转化成统一的键值对表示。

以下为

   1:  /// <summary>
   2:  /// Represents a source of configuration key/values for an application.
   3:  /// </summary>
   4:  public interface IConfigurationSource
   5:  {
   6:      /// <summary>
   7:      /// Builds the <see cref="IConfigurationProvider"/> for this source.
   8:      /// </summary>
   9:      /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
  10:      /// <returns>An <see cref="IConfigurationProvider"/></returns>
  11:      IConfigurationProvider Build(IConfigurationBuilder builder);
  12:  }

以下是MemoryConfigurationSource的源码

   1:  /// <summary>
   2:  /// Represents in-memory data as an <see cref="IConfigurationSource"/>.
   3:  /// </summary>
   4:  public class MemoryConfigurationSource : IConfigurationSource
   5:  {
   6:      /// <summary>
   7:      /// The initial key value configuration pairs.
   8:      /// </summary>
   9:      public IEnumerable<KeyValuePair<string, string>> InitialData { get; set; }
  10:   
  11:      /// <summary>
  12:      /// Builds the <see cref="MemoryConfigurationProvider"/> for this source.
  13:      /// </summary>
  14:      /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
  15:      /// <returns>A <see cref="MemoryConfigurationProvider"/></returns>
  16:      public IConfigurationProvider Build(IConfigurationBuilder builder)
  17:      {
  18:          return new MemoryConfigurationProvider(this);
  19:      }
  20:  }

IConfigurationProvider

通过上文的介绍,我们可以知道IConfigurationProvider是统一的对外接口,对用户提供配置的查询、重新加载等功能。其派生类包括Microsoft.Extensions.Configuration.ConfigurationProvider、Microsoft.Extensions.Configuration.ChainedConfigurationProvider、Microsoft.Extensions.Configuration.Memory.MemoryConfigurationProvider。另外该派生类还会在文件类配置场景下依赖Microsoft.Extensions.Configuration.FileExtensions组件。

以下是Microsoft.Extensions.Configuration.ConfigurationProvider的源码:

   1:  /// <summary>
   2:  /// Base helper class for implementing an <see cref="IConfigurationProvider"/>
   3:  /// </summary>
   4:  public abstract class ConfigurationProvider : IConfigurationProvider
   5:  {
   6:      private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
   7:   
   8:      /// <summary>
   9:      /// Initializes a new <see cref="IConfigurationProvider"/>
  10:      /// </summary>
  11:      protected ConfigurationProvider()
  12:      {
  13:          Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  14:      }
  15:   
  16:      /// <summary>
  17:      /// The configuration key value pairs for this provider.
  18:      /// </summary>
  19:      protected IDictionary<string, string> Data { get; set; }
  20:   
  21:      /// <summary>
  22:      /// Attempts to find a value with the given key, returns true if one is found, false otherwise.
  23:      /// </summary>
  24:      /// <param name="key">The key to lookup.</param>
  25:      /// <param name="value">The value found at key if one is found.</param>
  26:      /// <returns>True if key has a value, false otherwise.</returns>
  27:      public virtual bool TryGet(string key, out string value)
  28:          => Data.TryGetValue(key, out value);
  29:   
  30:      /// <summary>
  31:      /// Sets a value for a given key.
  32:      /// </summary>
  33:      /// <param name="key">The configuration key to set.</param>
  34:      /// <param name="value">The value to set.</param>
  35:      public virtual void Set(string key, string value)
  36:          => Data[key] = value;
  37:   
  38:      /// <summary>
  39:      /// Loads (or reloads) the data for this provider.
  40:      /// </summary>
  41:      public virtual void Load()
  42:      { }
  43:     
  44:      /// <summary>
  45:      /// Returns the list of keys that this provider has.
  46:      /// </summary>
  47:      /// <param name="earlierKeys">The earlier keys that other providers contain.</param>
  48:      /// <param name="parentPath">The path for the parent IConfiguration.</param>
  49:      /// <returns>The list of keys for this provider.</returns>
  50:      public virtual IEnumerable<string> GetChildKeys(
  51:          IEnumerable<string> earlierKeys,
  52:          string parentPath)
  53:      {
  54:          var prefix = parentPath == null ? string.Empty : parentPath + ConfigurationPath.KeyDelimiter;
  55:   
  56:          return Data
  57:              .Where(kv => kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
  58:              .Select(kv => Segment(kv.Key, prefix.Length))
  59:              .Concat(earlierKeys)
  60:              .OrderBy(k => k, ConfigurationKeyComparer.Instance);
  61:      }
  62:   
  63:      private static string Segment(string key, int prefixLength)
  64:      {
  65:          var indexOf = key.IndexOf(ConfigurationPath.KeyDelimiter, prefixLength, StringComparison.OrdinalIgnoreCase);
  66:          return indexOf < 0 ? key.Substring(prefixLength) : key.Substring(prefixLength, indexOf - prefixLength);
  67:      }
  68:   
  69:      /// <summary>
  70:      /// Returns a <see cref="IChangeToken"/> that can be used to listen when this provider is reloaded.
  71:      /// </summary>
  72:      /// <returns></returns>
  73:      public IChangeToken GetReloadToken()
  74:      {
  75:          return _reloadToken;
  76:      }
  77:   
  78:      /// <summary>
  79:      /// Triggers the reload change token and creates a new one.
  80:      /// </summary>
  81:      protected void OnReload()
  82:      {
  83:          var previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
  84:          previousToken.OnReload();
  85:      }
  86:   
  87:      /// <summary>
  88:      /// Generates a string representing this provider name and relevant details.
  89:      /// </summary>
  90:      /// <returns> The configuration name. </returns>
  91:      public override string ToString() => $"{GetType().Name}";
  92:  }

通过源码,我们可以知道ConfigurationProvider以字典类型缓存了多个Provider对象,有需要的时候,从内存中获取即可,配置的加载通过Load()方法实现,在ConfigurationRoot里我们介绍了其Reload,并且说明其方法是在循环调用ConfigurationProvider的Load方法,但是此处只提供了一个虚方法,其目的是要交给其他具体的Provider,比如环境变量、JSON、XML等,这些具体的Provider可以从相应的配置源中获取配置信息。所有的子节点KEY通过GetChildKeys方法实现,其重新加载方式通过ConfigurationReloadToken实例完成。

另外需要说明一下,在ConfigurationProvider构造函数里,对字典进行了初始化,并同时设置了字典Key不受大小写限制,这是一个需要注意的细节。

Configuration组件结构

通过查看.NET配置功能的源码,所有依赖均基于Microsoft.Extensions.Configuration.Abstractions,在其上有一层实现,即Microsoft.Extensions.Configuration,其内部也多数是抽象实现,并提供了多个虚方法交给其派生组件,比如环境变量、命令行参数、各种文件型配置等,当然各种文件型配置还要依赖Microsoft.Extensions.Configuration.FileExtensions组件。

以下是.NET Core 3.0预览版里的Configuration各个组件的结构图:

原文地址:https://www.cnblogs.com/lonelyxmas/p/10891626.html

时间: 2024-10-12 12:34:25

.NET Core 3.0之深入源码理解Configuration(一)的相关文章

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

原文:.NET Core 3.0之深入源码理解Configuration(二) 文件型配置基本内容 上一篇文章讨论了Configuration的几个核心对象,本文继续讨论Configuration中关于文件型配置的相关内容.相比较而言,文件型配置的使用场景更加广泛,用户自定义配置扩展也可以基于文件型配置进行扩展.如果需要查看上一篇文章,可以点击移步. .NET Core文件型配置中我们提供了三种主要的实现,分别是JSON.XML.INI,请查看下图 由图可知,这三种配置的实现方式是一样的,当然了

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

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

.NET Core 3.0之深入源码理解Startup的注册及运行

原文:.NET Core 3.0之深入源码理解Startup的注册及运行 写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程序的起点.通过使用Startup,可以配置化处理所有向应用程序所做的请求的管道,同时也可以减少.NET应用程序对单一服务器的依赖性,使我们在更大程度上专注于面向多服务器为中心的开发模式. 目录: Startup讨论 Starup所承担的角色 Startup编写规范 ConfigureServices C

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

写在前面 停了近一个月的技术博客,随着正式脱离996的魔窟,接下来也正式恢复了.本文从源码角度进一步讨论.NET Core 3.0 中关于Host扩展的一些技术点,主要讨论Long Run Program的创建与守护. 关于Host,我们最容易想到的就是程序的启动与停止,而其中隐藏着非常关键的功能,就是Host的初始化,我们所需要的所有资源都必须而且应该在程序启动过程中初始化完成,当然本文的主要内容并不是Host初始化,前文已经累述.当然,为了更好的守护与管理已经启动的Host,.NET Cor

【春华秋实】深入源码理解.NET Core中Startup的注册及运行

原文:[春华秋实]深入源码理解.NET Core中Startup的注册及运行 写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程序的起点.通过使用Startup,可以配置化处理所有向应用程序所做的请求的管道,同时也可以减少.NET应用程序对单一服务器的依赖性,使我们在更大程度上专注于面向多服务器为中心的开发模式. 目录: Startup讨论 Starup所承担的角色 Startup编写规范 ConfigureServices

Android # 4.0.x(1-3) 源码 下载 编译

Android 4.0源码下载方法:repo init -u https://android.googlesource.com/platform/manifest -b android-4.0.1_r1 官方下载页面:http://source.android.com/source/downloading.html Android SDK 4.0官方下载页面:http://developer.android.com/sdk/android-4.0.html android 4.0.3最新源码下载

Android7.0 Phone应用源码分析(三) phone拒接流程分析

接上篇博文:Android7.0 Phone应用源码分析(二) phone来电流程分析 今天我们再来分析下Android7.0 的phone的拒接流程 下面先来看一下拒接电话流程时序图 步骤1:滑动按钮到拒接图标,会调用到AnswerFragment的onDecline方法 com.android.incallui.AnswerFragment public void onDecline(Context context) { getPresenter().onDecline(context);

eclipse 编译tomcat8.0.26的源码

第一次写东西, 如果有不对的地方,请大神指正,我会尽快修正…… 参考:http://www.cnblogs.com/lanxuezaipiao/p/3640923.html 1.从tomcat官网(http://tomcat.apache.org/)下载 源码 apache-tomcat-8.0.26-src.zip . 2.下载项目构建工具 ant (http://ant.apache.org/).解压后, 配置环境变量,并修改path值. 例如,ANT_HOME="D:\Program Fi

Android7.0 Phone应用源码分析(二) phone来电流程分析

接上篇博文:Android7.0 Phone应用源码分析(一) phone拨号流程分析 今天我们再来分析下Android7.0 的phone的来电流程 1.1TelephonyFramework 当有来电通知时,首先接收到消息的是Modem层,然后Medoem再上传给RIL层,RIL进程通过sokcet将消息发送给RILJ(framework层的RIL),同样进入RILJ的processResponse方法,根据上一章节去电流程的分析得知,来电属于UnSolicited消息,事件ID是 RIL_