Overall introduction of Configuration

Microsoft has designed a new configuration system in .NET Core and implemented it in a very flexible and scalable way. From the source code point of view, its running mechanism is roughly, according to its Source, create a Builder instance, and will add a Provider to it, when we use the configuration information, we will get the corresponding Provider instance from the memory.

.NET Core uses a unified calling method to load different types of configuration information, and manages the configuration source through a unified abstract interface IConfigurationSource, which is just flexible. The extensibility is that we can customize the new Provider instance ourselves without changing the original calling method. The next article will be based on Consul, extending a new Provider instance.

In ASP.NET Core, our application configuration is based on the key-value pairs of IConfigurationProvider. Let’s take a look at the mind map:


DotNET_Core_3.0source-understanding_Configuration_(1)_0.png

 

Based on the chart, we can see that there are keys on the source variety, are:

Environmental variable

Command line argument

Various forms of configuration files

Memory object

User-defined extension source

Core object

Before introducing the .NET Core configuration features, let’s take a brief look at Microsoft.Extensions.Configuration.Abstractions, which abstracts the configuration capabilities of .NET Core and sets new standards for custom extensions. The four core objects described below all come from this component.

IConfiguration

The interface represents a set of key/value application configuration properties. The application uses the configuration entry object. The .NET Core has several extensions to it. The derived classes include the IConfigurationSection in the unified class library and the Microsoft.Extensions.Configuration class. ConfigurationRoot, ConfigurationSection, IConfigurationRoot in the library. We can get the IConfiguration instance through DI.

It mainly has the following three methods:

  • GetChildren(): Get the direct child configuration subsection
  • GetReloadToken(): Returns an IChangeToken that can be used to determine when to reload the configuration
  • GetSection(String): Get the child of the specified key

Let’s look at the source code:

   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);
  twenty three:   
  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 :           ChangeToken GetReloadToken ();
  35:       }

Usually we need to have sufficient flexibility in the configuration file, especially the configuration information we have extended is stored in other servers. When modifying, we need a set of monitoring functions to flexibly respond to the modification of configuration information. Now that .NET Core provides us with such a feature, we only need to customize a small amount of code to complete the synchronization of configuration information. This method is GetReloadToken(), and its return value is IChangeToken. Here is just a primer for synchronizing configuration information, which will be explained in detail later.

Since ConfigurationRoot and ConfigurationSection are gathered on the IConfiguration interface, these two classes are also discussed here, which makes us more visually impressed with the configuration features of .NET Core. These two interfaces are essentially the way .NET Core reads configuration information.

XML is a widely used data structure. When configuring XML, we usually use terms such as root node, parent node, and child nodes. The same is true here.

ConfigurationRoot is the root node of the configuration. It also implements IConfigurationRoot. This interface has only one method. Its main function is to implement reloading of configuration information. It also includes a collection property of type IConfigurationProvider. Its source code is as follows

   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:   }

The following is the implementation of ConfigurationRoot on Reload() method.

   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:   }

From the source code we know that if the Reload() method is called, all types of Providers will be reloaded.

In the previous configurationRoot means the root node of the configuration, then ConfigurationSection means non-following nodes. After all, the parent node and the child nodes are relative, so the non-root node is used here. ConfigurationSection inherits from IConfigurationSection. The interface has only three read-only attributes, which represent the Key, Value, and path information of the configuration information. It should be noted that the path information here mainly refers to the path from the root node to the current node to indicate the current The location of the node, similar to A:B:C, can represent the location of node C, where A, B, and C are the Keys of ConfigurationSection. The following is the source code of 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

This interface is mainly used to create IConfigurationProvider, and its derived classes include Microsoft.Extensions.Configuration.ConfigurationBuilder. Its members include

Two read-only properties:

  • Properties: Get the set of keys/values ​​that can be used to share data between IConfigurationBuilders
  • Sources: This attribute is used to cache different configuration sources for the creation of the corresponding Provider.

Two methods:

  • Add(IConfigurationSource source): Add IConfigurationSource and add it to the Sources in the properties.
  • Build(): This method traverses the Sources property and calls the Build() method of IConfigurationSource to get the Provider collection and finally create the IConfigurationRoot object.

ConfigurationBuilder source code is as follows

   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:       }

Here is a lot of emotion, we finally call the constructor of ConfigurationRoot, the reason is that the Provider provides a unified data access method, no matter what type of Provider, we can call its Load () method to load the configuration item . In addition, IConfigurationBuilder itself has a number of extension methods to register data sources, such as the AddJsonFile() extension method. Let’s take a look at our common way of writing,

   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

This interface represents the key-value pair of the application configuration. Its derived classes include Microsoft.Extensions.Configuration.ChainedConfigurationSource, Microsoft.Extensions.Configuration.Memory.MemoryConfigurationSource. In addition, the derived class will also depend on the Microsoft.Extensions.Configuration.FileExtensions component in the file class configuration scenario.

It is an abstract representation of all configuration sources, including JSON, XML, INI, environment variables, and more. As we also know from the above, IConfigurationBuilder will register multiple IConfigurationSource instances. It has only one method, the Build() method, and returns IConfigurationProvider. It can be seen that the creation of IConfigurationProvider depends on IConfigurationSource, which is also a one-to-one correspondence. All the different sources will eventually be transformed into a uniform key-value pair representation.

The following is

   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:   }

The following is the source code of 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

Through the above introduction, we can know that IConfigurationProvider is a unified external interface, providing users with configuration query, reloading and other functions. Its derived classes include Microsoft.Extensions.Configuration.ConfigurationProvider, Microsoft.Extensions.Configuration.ChainedConfigurationProvider, Microsoft.Extensions.Configuration.Memory.MemoryConfigurationProvider. In addition, the derived class will also depend on the Microsoft.Extensions.Configuration.FileExtensions component in the file class configuration scenario.

The following is the source code for 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:   }

Through the source code, we can know that the ConfigurationProvider caches multiple Provider objects by dictionary type. When needed, it can be retrieved from memory. The configuration load is implemented by Load() method. In ConfigurationRoot, we introduce its Reload and explain The method is to call the ConfigurationProvider’s Load method in a loop, but here only provides a virtual method, the purpose is to be handed over to other specific providers, such as environment variables, JSON, XML, etc., these specific providers can be configured from the corresponding Get configuration information from the source. All child nodes KEY are implemented by the GetChildKeys method, and the reloading method is completed by the ConfigurationReloadToken instance.

In addition, you need to explain that in the ConfigurationProvider constructor, the dictionary is initialized, and the dictionary key is set at the same time is not limited by the case. This is a detail that needs attention.

Configuration component structure

By looking at the source code of the .NET configuration function, all dependencies are based on Microsoft.Extensions.Configuration.Abstractions, and there is a layer implementation on it, namely Microsoft.Extensions.Configuration, which is mostly an abstract implementation and provides multiple virtual methods. Give it to its derived components, such as environment variables, command line parameters, various file types, etc. Of course, various file types depend on the Microsoft.Extensions.Configuration.FileExtensions component.

The following is a block diagram of the various components of Configuration in the .NET Core 3.0 Preview:

DotNET_Core_3.0source-understanding_Configuration_(1)_1.png

Orignal link:https://www.cnblogs.com/edison0621/p/10854215.html