ASP.NET Core 2.2:. Twenty-three in-depth chat with the internal configuration of the processing mechanism

The previous chapter provides configuration of multiple data sources to be registered, loading and acquisition process, this section look at how this process system is implemented. ( ASP.NET Core Series Catalog )

A registered data source

The data source is provided in the previous section, appsettings.json, command line environment variable are three ways to automatically load the system, because the system has to feed the three data sources in webHost.CreateDefaultBuilder (args) registration, then start with this method. This method is also called ConfigureAppConfiguration method, the code is as follows:

public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
    var builder = newWebHostBuilder();
    //省略部分代码
    builder.UseKestrel((builderContext, options) =>
        {
            options.Configure(builderContext.Configuration.GetSection("Kestrel"));
        })
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            var env = hostingContext.HostingEnvironment;
            config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional:true, reloadOnChange: true);
            if(env.IsDevelopment())
            {
                var appAssembly = Assembly.Load(newAssemblyName(env.ApplicationName));
                if(appAssembly != null)
                {
                    config.AddUserSecrets(appAssembly, optional: true);
                }
            }

            config.AddEnvironmentVariables();
            if(args =! null ) 
            { 
                config.AddCommandLine (args); 
            } 
       }) 

       // omit part of the code 

    return Builder; 
}

 

Look ConfigureAppConfiguration these methods, there are four loaded content, the first load is appsettings.json and appsettings. {Env.EnvironmentName} .json two JSON files on env.EnvironmentName have said in previous chapters, common there development, Staging and Production three values, in our development and debugging generally development, which is loaded appsettings.json and appsettings. Development.json two JSON files. The second is the user load confidential documents, which is limited to the state under Development, it will be loaded by config.AddUserSecrets method. A third method is to load by config.AddEnvironmentVariables environment variables, command line arguments fourth is loaded by config.AddCommandLine method.

Note: This method ConfigureAppConfiguration this time will not be executed, but this method as an Action <WebHostBuilderContext, IConfigurationBuilder> configureDelegate added to the _configureServicesDelegates property WebHostBuilder in. configureServicesDelegates List is a collection of <Action <WebHostBuilderContext, IConfigurationBuilder >> type. Corresponding to the code is as follows:

public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate)
{
    if(configureDelegate == null)
    {
        throw new ArgumentNullException(nameof(configureDelegate));
    }

    _configureAppConfigurationBuilderDelegates.Add(configureDelegate);
    returnthis;
}

 

On an example, we called after webHost.CreateDefaultBuilder (args) method again ConfigureAppConfiguration way to add some custom data sources, this method is not executed, the same is added to this collection. Create a WebHost until WebHostBuilder through its Build () method when will traverse the collection executed one by one. This code is written in BuildCommonServices is called Build () method () in:

private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
    //省略部分代码
    var builder = new ConfigurationBuilder()
        .SetBasePath(_hostingEnvironment.ContentRootPath)
        .AddConfiguration(_config);

    foreach (var configureAppConfiguration in _configureAppConfigurationBuilderDelegates)
    {
        configureAppConfiguration(_context, builder);
    }

    var configuration = builder.Build();
    services.AddSingleton<IConfiguration>(configuration);
    _context.Configuration= Configuration;
 // partially omitted codes 
    return Services; 
}

 

First we create a ConfigurationBuilder object, and then executed one by one to be added to the collection _configureAppConfigurationBuilderDelegates in configureAppConfiguration method, then in the implementation of these different data sources are loaded by how foreach loop it? This part of the function namespace Microsoft.Extensions.Configuration namespace.

To appsettings.json corresponding config.AddJsonFile ( "appsettings.json", optional: true, reloadOnChange: true) method, for example, a closer look at its implementation. First introduced IConfigurationBuilder interface implementation class corresponding to a ConfigurationBuilder, code is as follows:

public class ConfigurationBuilder : IConfigurationBuilder
    {
        public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();

        public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>();

        public IConfigurationBuilder Add(IConfigurationSource source)
        {
            if (source == null)
            {
                throw newArgumentNullException The (NameOf (Source)); 
            } 

            Sources.Add (Source); 
            return  the this ; 
        } 
        // omitted IConfigurationRoot Build () method, described later 
    }

 

AddJsonFile class method calls from JsonConfigurationExtensions ConfigureAppConfiguration method, as follows:

public static class 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(Resources.Error_InvalidFilePath, nameof(path));
        }

        return builder.AddJsonFile(s =>
        {
            s.FileProvider = provider;
            s.Path = path;
            s.Optional = optional;
            s.ReloadOnChange = reloadOnChange;
            s.ResolveFileProvider();
        });
    }
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource)
        => builder.Add(configureSource);
}

 

AddJsonFile method creates a JsonConfigurationSource and added via Add (IConfigurationSource source) This method ConfigurationBuilder ConfigurationBuilder JsonConfigurationSource to the IList <IConfigurationSource> Sources set and go.

Similarly, for the environment variable, there is a corresponding EnvironmentVariablesExtensions, creates a corresponding EnvironmentVariablesConfigurationSource ConfigurationBuilder add to the IList <IConfigurationSource> Sources and set to go. Such CommandLineConfigurationSource also CommandLineConfigurationExtensions and the like, the final result is based on the order of loading the data source, to generate a plurality of XXXConfigurationSource objects (which are directly or indirectly implements IConfigurationSource Interface) is added to ConfigurationBuilder IList <IConfigurationSource> Sources and set.

ConfigureAppConfiguration method WebHost.CreateDefaultBuilder Program file (args) method is called, if after re ConfigureAppConfiguration CreateDefaultBuilder method calls the method and add a data source (like the one example), it will generate the corresponding objects XXXConfigurationSource ConfigurationBuilder added to the IList <IConfigurationSource> Sources and set.

NOTE: This is not for each data source generates a XXXConfigurationSource, but according to every addition generates a XXXConfigurationSource, and follows the order added. Such as adding multiple JSON file, generates multiple JsonConfigurationSource.

The relationship between these ConfigurationSource 1 below:

 

figure 1

Here the collection of various sources of data is completed, are added to the IList ConfigurationBuilder <IConfigurationSource> Sources attribute.

Back BuildCommonServices method, one by one to perform a method of acquiring configureAppConfiguration IList <IConfigurationSource> foreach loop through after the next one varconfiguration = builder.Build (), which is a call to the Build ConfigurationBuilder () method creates a IConfigurationRoot object. Corresponding to the code is as follows:

public class ConfigurationBuilder : IConfigurationBuilder
    {
        public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();

        //省略部分代码

        public IConfigurationRoot Build()
        {
            var providers = new List<IConfigurationProvider>();
            foreach (var source in Sources)
            {
                var provider = source.Build(this);
                providers.Add(provider);
            }
            return new ConfigurationRoot(providers);
        }

    }

 

This method is mainly two processes: Firstly, traversing IList <IConfigurationSource> Sources set, the main method for each call the Build IConfigurationSource created therein corresponding IConfigurationProvider, ultimately generate a List <IConfigurationProvider>; second, set by List <IConfigurationProvider> created ConfigurationRoot. ConfigurationRoot realized IConfigurationRoot interface.

The first look at the process, still to JsonConfigurationSource example, the code is as follows:

    public class JsonConfigurationSource : FileConfigurationSource
    {
        public override IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            EnsureDefaults(builder);
            return new JsonConfigurationProvider(this);
        }
    }

 

JsonConfigurationSource creates an object named JsonConfigurationProvider through the Build method. By JsonConfigurationProvider name shows that it is for the JSON type is meant IConfigurationProvider type IConfigurationSource creation of different types is not the same, corresponding to Figure 18-4 IConfigurationSource, generated IConfigurationProvider relationship shown in Figure 2.

 

figure 2

Added to the system a plurality of data sources is converted into a corresponding one ConfigurationProvider, ConfigurationProvider which constitute a set of ConfigurationProvider.

Look again at the second procedure, Build method of ConfigurationBuilder last sentence is ConfigurationProvider return a collection of new ConfigurationRoot (providers), it is through the process of creating the first creation ConfigurationRoot. ConfigurationRoot code is as follows:

public class ConfigurationRoot : IConfigurationRoot
    {
        private IList<IConfigurationProvider> _providers;
        private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();

        public ConfigurationRoot(IList<IConfigurationProvider> providers)
        {
            if (providers == null)
            {
                throw new ArgumentNullException(nameof(providers));
            }

            _providers = providers;
            foreach (var p inProviders) 
            { 
                p.Load (); 
                ChangeToken.OnChange (() => p.GetReloadToken (), () => RaiseChanged ()); 
            } 
        } 
// omit part of the code 
}

 

As can be seen, the main role is to ConfigurationRoot ConfigurationProvider constructor set as the value of a property of itself, and traverse this collection, Load ConfigurationProvider one by one of these calls, and a data source for binding OnChange change notification method of ChangeToken and processing methods.

Second, the loading of the data source

Can be seen from Figure 18-5, all types of data sources to create the ultimate XXX ConfigurationProvider are inherited from ConfigurationProvider, so they have a Load method and a IDictionary <string, string> Data types of attributes that are important to the core of the system configuration. Load a method for reading and processing the data source, and configured to store the final results Data. Loading the entire configuration data is completed by the system calls the Load method of one by the Provider.

To JsonConfigurationProvider example, it inherits from FileConfigurationProvider, so look at FileConfigurationProvider code:

public  abstract  class FileConfigurationProvider: ConfigurationProvider 
{ 
// omit part of the code 
    Private  void the Load ( BOOL reload) 
    { 
        var File = Source.FileProvider? .GetFileInfo (Source.Path);
         IF (File == null ||! File.Exists) 
        { 
        / / omit part of the code 
        }
         the else 
        { 
            IF (reload) 
            { 
                the Data = new new the Dictionary < String , String >(StringComparer.OrdinalIgnoreCase);
            }
            using (var stream = file.CreateReadStream())
            {
                try
                {
                    Load(stream);
                }
                catch (Exception e)
                {
//省略部分代码
                }
            }
        }
        OnReload();
    }
    public override void Load()
    {
        Load(reload: false);
}
    public abstract void Load(Stream stream);
} 

The main function of this section of code is generated to read the file stream, then call Load (stream) method parses the file contents. Seen from FIG. 18-5, JsonConfigurationProvider, IniConfigurationProvider, XmlConfigurationProvider are inherited from FileConfigurationProvider, to correspond JSON, INI, XML three data sources, the content of the document but a different format, it will function to read the file contents common AC He gave FileConfigurationProvider to complete, and the three sub-classes ConfigurationProvider FileConfigurationProvider only need to read the contents of a file can be resolved. Therefore this parameter is written in the stream Load method subclasses such JsonConfigurationProvider, IniConfigurationProvider, XmlConfigurationProvider in a special file format corresponding to the processing itself.

JsonConfigurationProvider code is as follows:

public class JsonConfigurationProvider : FileConfigurationProvider
{
    public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }

    public override void Load(Stream stream)
    {
        try
        {
            Data = JsonConfigurationFileParser.Parse(stream);
        }
        catch (JsonReaderException e)
        {
            string errorLine = string.Empty;
            if (stream.CanSeek)
            {
                stream.Seek(0, SeekOrigin.Begin);

                IEnumerable<string> fileContent;
                using (var streamReader = new StreamReader(stream))
                {
                    fileContent = ReadLines(streamReader);
                    errorLine = RetrieveErrorContext(e, fileContent);
                }
            }

            throw new FormatException(Resources.FormatError_JSONParseError(e.LineNumber, errorLine), e);
        }
    }
   //省略部分代码
}

 

JsonConfigurationProvider about parsing JSON file by JsonConfigurationFileParser.Parse (stream) completed. The final analysis result is assigned to a property named Data in the parent class ConfigurationProvider.

Therefore, the final content of each data source are respectively become resolved IDictionary <string, string> set, this set as an attribute of the corresponding ConfigurationProvider. The collection is composed of many ConfigurationProvider and as the property ConfigurationRoot. Their final diagram below 3:

 

image 3

This, conversion work load and configuration data is completed. Figure 4 below shows this process.

 

 

Figure 4

 

Third, the configuration of reading

Examples of the first section by _configuration: get [ "Theme Color"] way to a configuration corresponding to the value, which is how to achieve it? Now that we know the source of the data loading process, and this is _configuration after the data source is loaded final output, ie ConfigurationRoot, shown in Figure 18-7. Its code is as follows:

public class ConfigurationRoot : IConfigurationRoot
{
    private IList<IConfigurationProvider> _providers;
    private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();

    //省略了上文已讲过的构造方法

    public IEnumerable<IConfigurationProvider> Providers => _providers;
    public string this[string key]
    {
        get
        {
            foreach (var provider in _providers.Reverse())
            {
                string value;

                if (provider.TryGet(key, out value))
                {
                    return value;
                }
            }

            return null;
        }

        set
        {
            if (!_providers.Any())
            {
                throw new InvalidOperationException(Resources.Error_NoSources);
            }

            foreach (var provider in _providers)
            {
                provider.Set(key, value);
            }
        }
    }

    public IEnumerable<IConfigurationSection> GetChildren() => GetChildrenImplementation(null);

    internal IEnumerable<IConfigurationSection> GetChildrenImplementation(string path)
    {
        return _providers
            .Aggregate(Enumerable.Empty<string>(),
                (seed, source) => source.GetChildKeys(seed, path))
            .Distinct()
            .Select(key => GetSection(path == null ? key : ConfigurationPath.Combine(path, key)));
    }

    public IChangeToken GetReloadToken() => _changeToken;

    public IConfigurationSection GetSection(string key) 
        => new ConfigurationSection(this, key);

    public void Reload()
    {
        foreach (var provider in _providers)
        {
            provider.Load();
        }
        RaiseChanged();
    }

    private void RaiseChanged()
    {
        var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
        previousToken.OnReload();
    }
}

 

对应_configuration["Theme:Color"]的读取方式的是索引器“string this[string key]”,通过查看其get方法可知,它是通过倒序遍历所有ConfigurationProvider,在ConfigurationProvider的Data中尝试查找是否存在Key为"Theme:Color"的值。这也说明了第一节的例子中,在Theme.json中设置了Theme对象的值后,原本在appsettings.json设置的Theme的值被覆盖的原因。从图18‑6中可以看到,该值其实也是被读取并加载的,只是由于ConfigurationRoot的“倒序”遍历ConfigurationProvider的方式导致后注册的Theme.json中的Theme值先被查找到了。同时验证了所有配置值均认为是string类型的约定。

ConfigurationRoot还有一个GetSection方法,会返回一个IConfigurationSection对象,对应的是ConfigurationSection类。它的代码如下:

public class ConfigurationSection : IConfigurationSection
    {
        private readonly ConfigurationRoot _root;
        private readonly string _path;
        private string _key;

        public ConfigurationSection(ConfigurationRoot root, string path)
        {
            if (root == null)
            {
                throw new ArgumentNullException(nameof(root));
            }

            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            _root = root;
            _path = path;
        }

        public string Path => _path;
        public string Key
        {
            get
            {
                if (_key == null)
                {
                    // Key is calculated lazily as last portion of Path
                    _key = ConfigurationPath.GetSectionKey(_path);
                }
                return _key;
            }
        }
        public string Value
        {
            get
            {
                return _root[Path];
            }
            set
            {
                _root[Path] = value;
            }
        }
        public string this[string key]
        {
            get
            {
                return _root[ConfigurationPath.Combine(Path, key)];
            }

            set
            {
                _root[ConfigurationPath.Combine(Path, key)] = value;
            }
        }

        public IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key));

        public IEnumerable<IConfigurationSection> GetChildren() => _root.GetChildrenImplementation(Path);

        public IChangeToken GetReloadToken() => _root.GetReloadToken();
}

 

它的代码很简单,可以说没有什么实质的代码,它只是保存了当前路径和对ConfigurationRoot的引用。它的方法大多是通过调用ConfigurationRoot的对应方法完成的,通过它自身的路径计算在ConfigurationRoot中对应的Key,从而获取对应的值。而ConfigurationRoot对配置值的读取功能以及数据源的重新加载功能(Reload方法)也是通过ConfigurationProvider实现的,实际数据也是保存在ConfigurationProvider的Data值中。所以ConfigurationRoot和ConfigurationSection就像一个外壳,自身并不负责数据源的加载(或重载)与存储,只负责构建了一个配置值的读取功能。

而由于配置值的读取是按照数据源加载顺序的倒序进行的,所以对于Key值相同的多个配置,只会读取后加载的数据源中的配置,那么ConfigurationRoot和ConfigurationSection就模拟出了一个树状结构,如下图5:

 

图5

本图是以如下配置为例:

{
  "Theme": {
    "Name": "Blue",
    "Color": "#0921DC"
  }
}

 

ConfigurationRoot利用它制定的读取规则,将这样的配置模拟成了如图18‑8这样的树,它有这样的特性:

A.所有节点都认为是一个ConfigurationSection,不同的是对于“Theme”这样的节点的值为空(图中用空心椭圆表示),而“Name”和“Color”这样的节点有对应的值(图中用实心椭圆表示)。

B.由于对Key值相同的多个配置只会读取后加载的数据源中的配置,所以不会出现相同路径的同名节点。例如第一节例子中多种数据源配置了“Theme”值,在这里只会体现最后加载的配置项。

四、配置的更新

由于ConfigurationRoot未实际保存数据源中加载的配置值,所以配置的更新实际还是由对应的ConfigurationProvider来完成。以JsonConfigurationProvider、IniConfigurationProvider、XmlConfigurationProvider为例,它们的数据源都是具体文件,所以对文件内容的改变的监控也是放在FileConfigurationProvider中。FileConfigurationProvider的构造方法中添加了对设置了对应文件的监控,当然这里会首先判断数据源的ReloadOnChange选项是否被设置为True了。

    public abstract class FileConfigurationProvider : ConfigurationProvider
    {
        public FileConfigurationProvider(FileConfigurationSource source)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            Source = source;

            if (Source.ReloadOnChange && Source.FileProvider != null)
            {
                changeToken.OnChange(
                    () => Source.FileProvider.Watch(Source.Path),
                    () => {
                        Thread.Sleep(Source.ReloadDelay);
                        Load(reload: true);
                    });
            }
        }
       //省略其他代码
}

 

所以当数据源发生改变并且ReloadOnChange被设置为True的时候,对应的ConfigurationProvider就会重新加载数据。但ConfigurationProvider更新数据源也不会改变它在ConfigurationRoot的IEnumerable<IConfigurationProvider>列表中的顺序。如果在列表中存在A和B两个ConfigurationProvider并且含有相同的配置项,B排在A后面,那么对于这些相同的配置项来说,A中的是被B中的“覆盖”的。即使A的数据更新了,它依然处于“被覆盖”的位置,应用中读取相应配置项的依然是读取B中的配置项。

五、配置的绑定

在第一节的例子中讲过了两种获取配置值的方式,类似这样_configuration["Theme:Name"]和_configuration.GetValue<string>("Theme:Color","#000000")可以获取到Theme的Name和Color的值,那么就会有下面这样的疑问:

appsettings.json中存在如下这样的配置

{
  "Theme": {
    "Name": "Blue",
    "Color": "#0921DC"
  }
}

 

新建一个Theme类如下:

    public class Theme
    {
        public string Name { get; set; }
        public string Color { get; set; }
    }

 

是否可以将配置值获取并赋值到这样的一个Theme的实例中呢?

当然可以,系统提供了这样的功能,可以采用如下代码实现:

     Theme theme = new Theme();
     _configuration.GetSection("Theme").Bind(theme);

 

绑定功能由ConfigurationBinder实现,逻辑不复杂,读者如果感兴趣的可自行查看其代码。

 

Guess you like

Origin www.cnblogs.com/FlyLolo/p/ASPNETCore_23.html