ASP.NET Core的配置(2):配置模型详解

上面一章我们以实例演示的方式介绍了几种读取配置的几种方式,其中涉及到三个重要的对象,它们分别是承载结构化配置信息的Configuration,提供原始配置源数据的ConfigurationProvider,以及作为“中间人”的ConfigurationBuilder。接下来我们将会对由这三个核心对象组成的配置模型进行详细介绍,不过在此之前我们有必要来认识配置信息在不同载体中所体现出来的三种结构。

一、配置的三种结构

相同的数据具有不同的表现和承载方式,同时体现出不同的数据结构。对于配置来说,它在消费过程中是以Configuration对象的形式来体现的,该对象逻辑上具有一个树形化的层次结构。配置具有多种来源,可以是内存对象、物理文件或者数据库,不同类型的数据源决定了不同的配置结构。我们将这两种结构称为逻辑结构和原始结构。在这两种结构之间,配置还存在一种中间结构,我们姑且称之为物理结构。

逻辑结构

配置的逻辑结构就是Configuration对象所体现的结构,说得更加准确一点应该是针对Configuration对象的API所体现的结构(因为不是所有的Configuration对象内部都封装一组配置数据)。配置在逻辑上呈现为一种树形结构,我们称之为配置树,组成这棵树的某个节点就体现为一个Configuration对象。表现为键值对的原子配置项存储于叶子节点中,而非叶子节点仅仅体现为一个配置节点的逻辑容器,自身不包含具体的配置数据。对于我们在第一节定义的FormatSettings来说,它对应的配置具有如右图所示的逻辑结构。

原始结构

配置采用怎样的原始结构取决于我们采用何种方式定义它。最常见的配置源体现为采用某个格式的文本文件,那么配置的原始结构则由文件的格式来决定。对于我们在第一节定义的FormatSettings类型,我们可以按照如下的形式以XML和JSON的格式来定义其配置。

XML:


JSON:


物理结构

配置模型的终极目的就是将配置从原始结构转换成逻辑结构。不过在进行结构转化的时候,它并不会直接将原始的配置数据转换成一个Configuration对象,它们之间由一种被我称为物理结构的中间结构作为过度。配置的物理结构体现为一个简单的数据字典。同样是针对FormatSettings这个类型,对应的配置将具有如下表所示的物理结构。

结构转换

配置模型的终极目的在于将具有不同来源的配置转换成Configuration对象,配置源和Configuration对象本身分别体现了配置的原始结构和逻辑结构,所以配置模型旨在实现配置数据从原始结构向逻辑结构的转换。在具体转换过程中,配置模型先利用与配置源相对应的ConfigurationProvider将配置数据从原始结构转换成体现为数据字典的物理结构。当我们利用ConfigurationBuilder生成Configuration的时候,实际上将配置数据从物理结构转换成逻辑结构。

二、Configuration

我们在上面以数据结构转换的角度分析了Configuratin、ConfigurationProvider和ConfigurationBuilder这三个核心对象在配置模型中所起的作用,接下来让我们来更加深入地认识它们。我们首先来介绍Configuration对象,本章不断提及的Configuration泛指类型实现了IConfiguration接口的对象,该接口定义在“Microsoft.Extensions.Configuration”命名空间下,如果未作特别说明,本章涉及到的与配置相关的类型均定义在此命名空间下。


配置具有树形逻辑结构,一个Configuration对象表示配置树的某个配置节点。对于组成整棵树的所有配置节点来说,表示根节点的Configuration对象与表示其它配置节点的Configuration对象相比具有不同的特性,所以配置模型采用不同的接口来表示它们。具体来说,基于根节点的Configuration对象通过接口IConfigurationRoot表示,另一个接口IConfigurationSection则表示针对非空节点的Configuration对象,两个接口都继承IConfiguration。如右图所示,一棵完整的配置树由一个ConfigurationRoot对象和若干ConfigurationSection构成。

ConfigurationRoot

我们将所有实现了IConfigurationRoot接口的类型和其对象统称为ConfigurationRoot。如下面的代码片段所示,IConfigurationRoot仅仅包含一个唯一的方法Reload实现对配置数据的重新加载。一个ConfigurationRoot对象表示配置数的根节点,如果它被重新加载了,那么这颗配置树承载的所有配置数据均被重新加载了。


ConfigurationSection

我们将所有实现了IConfigurationSection接口的类型及其对象统称为ConfigurationSection,一个ConfigurationSection对应着配置树中某个非根配置节。IConfigurationSection具有如下三个属性,只读属性Key用来唯一标识多个“同父”配置节,而另一个只读属性Path则表示从根节点到父节点的路径,该路径由ConfigurationSection的Key组成,并采用冒号作为分隔符。Path和Key的组合体现了当前配置节在整个配置树中的位置。


IConfigurationSection的Value属性表示配置节的值,在大部分情况下,只有配置树叶子结点对应的ConfigurationSection对象才具有值,非叶子节点对应的ConfigurationSection对象实际上仅仅表示一组隶属于它的所有子配置节的逻辑容器,它们的Value一般返回Null。值得一体的是,这个Value属性并不是只读的,而是可读可写的。

在对ConfigurationRoot和ConfigurationSection具有基本了解情况下我们回过头来看看定义在接口IConfiguration中的成员。它的GetChildren方法返回一组表示其子配置节的ConfigurationSection对象集合,另一个方法GetSection则根据指定的Key返回对应的ConfigurationSection对象。当GetSection方法执行的时候,指定的参数将会与当前ConfigurationSection的Path进行组合以确定目标ConfigurationSection所在的路径,所以如果在调用该方法的时候指定一个相对于当前配置节的路径,我们是可以得到子节点以下的某个配置节。

如上面的代码片段所示,我们以不同的方式调用GetSection方法得到的都是路径为“Format:DateTime:LongDatePattern”的ConfigurationSection。上面这段代码还体现了另一个有趣的现象,虽然这三个ConfigurationSection对象均指向配置树的同一个节点,但是它们却并非同一个对象。换句话说,当我们调用GetSection方法的时候,不论配置树种是否存在一个与指定路径匹配的配置节,它总是会创建一个全新的ConfigurationSection对象。

IConfiguration还具有一个索引,我们可以指定子配置节的Key或者相对当前配置节的路径得到对应配置节的值。当这个索引执行的时候,它会按照与GetSection方法完全一致的逻辑得到一个ConfigurationSection对象,并返回其Value属性。如果配置树中不具有匹配的配置节,该索引会返回Null而不会抛出异常。

三、ConfigurationProvider

为配置模型提供原始配置数据的ConfigurationProvider是对所有实现了IConfigurationProvider接口的所有类型及其对象的统称。从配置数据结构转换的角度来看,ConfigurationProvider的目的在于将配置数据从原始结构转换成物理结构,由于配置数据的物理结构体现为一个简单的二维数据字典,所以我们会发现定义在IConfigurationProvider接口中的方法大都体现为针对字典对象的相关操作。

配置数据的加载通过调用ConfigurationProvider的Load方法来完成。我们可以调用TryGet方法获取有指定的Key所标识的配置项的值。从数据持久化的角度来讲,ConfigurationProvider基本上都是只读的,也就是说ConfigurationProvider只负责从持久化资源中读取配置数据,而不负责更新保存在持久化资源的配置数据,所以它提供的Set方法设置的配置数据一般只会保存在内存中。ConfigurationProvider的GetChildKeys方法用于获取指定路径对应配置节的所有子节点的Key。

每种不同类型的配置源都具有对应的ConfigurationProvider,它们对应的类型大都不会直接实现IConfigurationProvider接口,而是继承另一个名为ConfigurationProvider的抽象类。这个抽象类的定义其实很简单,从如下的代码片段可以看出它仅仅是对一个IDictionary<string, string>对象的封装,其Set和TryGetValue方法最终操作的都是这个字典对象。它实现了Load方法并将其定义成虚方法,具体的ConfigurationProvider可以通过重写这个方法从相应的数据源中读取配置数据并对这个字典对象进行初始化。

接下来我们简单介绍一下定义在这个抽象类中GetChildKeys方法的逻辑。采用基于路径的Key让数据字典在逻辑上具有了树形化层次结构,而这个方法用于获取将指定配置节作为父节点的所有配置节的Key。指定的父配置节通过参数parentPath表示的路径来体现,另一个参数delimiter则表示路径采用的分隔符。除此之外,这个方法还具有一个字符串集合类型的参数earlierKeys,它表示预先解析出来的Key,这个列表会包含在返回的结果中。

 

为了让读者朋友们能够更加直观地理解GetChildKeys方法的逻辑,我们编写了如上一段实例程序。我们创建了一个MemoryConfigurationProvider对象,由塔封装的配置数据字段包含三个元素,它们对应的Key分别是“A:B:C”、“A:B:D”和“A:E”。我们调用它的GetChildKeys方法并将表示父节点的路径分别指定为“A”、“A:B和“A:B:C”以获取相应子节点的Key。除此之外,我们采用冒号(“:”)作为分隔符,并将earlierKeys指定为包含“X”、“Y”和“Z”三个元素的数组。这段程序执行之后会在控制台上产生如下的输出结果,我们从中可以看出一个细节,返回的结构并没有将重复的Key剔除

 

四、ConfigurationBuilder

ConfigurationBuilder泛指所有实现了IConfigurationBuilder接口的类型及其对象,它在配置模型中的作用就是利用注册的ConfigurationProvider提取转换成数据字典的配置数据并创建对应的Configuration对象,具体来说创建的是一个体现配置树的ConfigurationRoot对象。注册到ConfigurationBuilder上的ConfigurationProvider体现为IConfigurationBuilder接口的Providers属性,我们可以调用Add方法将ConfigurationProvider添加到这个集合中。

 

除此之外,IConfigurationBuilder还具有一个字典类型的只读属性Properties,我们可以将任意自定义的属性附加当一个ConfigurationBuilder对象上,并通过对应的Key得到这些属性值。ConfigurationRoot的创建最终通过Build方法完成。

原生的配置模型中提供了一个实现IConfigurationBuilder接口的类型,那就是在我们之前演示的实例中多次使用的ConfigurationBuilder类,配置模型默认的配置生成机制体现在它实现的Build方法中。具体来说,实现在ConfigurationBuilder类中的Build方法返回对象的真实类型为ConfigurationRoot,该对象通过一个类型为ConfigurationSection对象表示非根配置节。右图所示的UML展示了配置模型中以Configuration、ConfigurationProvider和ConfigurationBuilder为核心的相关接口/类型以及它们之前的关系。

ConfigurationRoot和ConfigurationSection这个两个类型的定义体现配置模型默认采用怎样的机制读取配置数据,这是我们本节论述的重点内容。虽然配置模型最终提供的配置数据通过Configuration对象来体现,但是不论ConfigurationRoot还是ConfigurationSection对象,它们自身本没有封装任何的形式的配置数据所有针对它们的数据读写操作最终都会转移到相应的ConfigurationProvider上。由于Configuration对象仅仅体现为ConfigurationProvider的代理,所以由同一个ConfigurationBuilder创建的所有ConfigurationRoot对象都是等效的,下面的代码片段体现了这样的等效性。

 

组成配置树的ConfigurationRoot和ConfigurationSection不但自身封装和配置数据,配置节父子关系的维护也并不直接通过对象之间的引用关系来维系。如右图所示,对于一个表示配置树中某个非根配置节的ConfigurationSection对象来说,它仅仅保留着对根节点的引用,后者是一个类型为ConfigurationRoot的对象。当我们调用ConfigurationSection方法获取或者设置配置数据的时候,它会直接将调用请求转发给表示配置树根的ConfigurationRoot对象。

具体来说,当我们试图通过某个ConfigurationSection对象得到对应配置节点的值时,该对象会将配置数据的读取请求转发给它所引用的表示数值树根的ConfigurationRoot对象,同时将自身的路径一并传递给后者。ConfigurationRoot最终利用ConfigurationProvider根据指定的路径得到对应配置项的值,左图揭示了这样的流程。

在对实现在ConfigurationRoot和ConfigurationSection这两个类中针对配置的读写机制有了大概的了解之后,我们从代码实现的角度来进一步地来认识这两个类型,在这之前我们需要先来认识一个名为ConfigurationPath的工具类。顾名思义,ConfigurationPath帮助我们实现针对配置树路径相关的计算,其中Combine方法将多个片段合并成一个完整的路径,GetSectionKey方法会根据指定的路径得到对应的Key,而GetParentPath则根据指定的路径得到上一级的路径。


ConfigurationRoot

ConfigurationRoot真实的实现逻辑基本上体现在如下所示的代码片段中。一个ConfigurationRoot对象维护着一组提供原始配置数据的ConfigurationProvider对象,每一次对配置数据的读写操作最终都会转移到它们头上。当调用它的索引指定相应的Key获取对应配置项的值时,我们会将这组ConfigurationProvider对象进行逆向排序,并将指定的Key作为参数依次调用每个ConfigurationProvider的TryGet方法直到该方法返回True,并将这个方法返回的值最为索引最终的返回值。当我们利用索引对指定配置项的值进行设置的时候,实际上会调用每个ConfigurationProvider的Set方法。

 

ConfigurationRoot实现在索引中读取配置的逻辑体现了配置模型一个重要的特性,那就是如果某个配置项的数据具有多个来源,那么最后添加到ConfigurationBuilder中的ConfigurationProvider具有更高的优先级,我们姑且将这个特性称为ConfigurationProvider“后来居上”的原则。如果希望覆盖应用现有的某个配置,我们只需要将提供新配置的ConfigurationProvider添加到ConfigurationBuilder之上即可。

我们定义了一个辅助方法GetChildrenCore来获取某个配置节的所有子配置节,这个指定的配置节通过作为参数的路径来表示。当这个方法执行之后,所有ConfigurationProvider的GetChildKeys方法会被调用以获取所有子配置节的Key,我们利用它们生成表示配置节的ConfigurationSection对象。在实现的GetChildren方法中,我们会调用这个方法来获取隶属于自己的所有子配置节。而另一个GetSection方法中,我们直接返回根据指定路径(对于表示根配置节来说,参数key表示配置节的路径)创建的ConfigurationSection对象。

ConfigurationSection

在上面关于用于模拟ConfigurationRoot类型定义的代码中我们知道最终表示非根配置节的ConfigurationSection对象是根据它的路径和作为根配置节的ConfigurationRoot对象创建的。ConfigurationRoot将配置的读写操作递交给相应的ConfigurationProvider来完成,而ConfigurationSection则将委托自己的根配置节来完成读写配置的操作,这样的策略体现在如下所示的代码中。


[1] ForEach是为了让代码尽量精练而为类型IEnumerable<T>定义的扩展方法,在后续章节中我们会经常使用到它。

相关文章:

  • ASP.NET Core 1.0 入门——了解一个空项目

  • ASP.NET Core 1.0 部署 HTTPS (.NET Framework 4.5.1)

  • .NET Core 1.0、ASP.NET Core 1.0和EF Core 1.0简介

  • 云服务器下ASP.NET Core 1.0环境搭建(包含mono与coreclr)

  • 使用VS Code开发ASP.NET Core 应用程序

  • dotnet run是如何启动asp.net core站点的

  • ASP.NET Core提供模块化Middleware组件

  • “dotnet restore"和"dotnet run"都做了些什么?

  • 探秘 dotnet run 如何运行 .NET Core 应用程序

  • .NET Portability Analyzer 已开源

  • ASP.NET Core的配置(1):读取配置信息

原文地址:http://www.cnblogs.com/artech/p/asp-net-core-config-02.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/329618.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mysql的安装与远程登录

一、安装 &#xff08;1&#xff09;linux版A.linux普通版http://www.jb51.net/article/104107.htmB.ubuntu版 1.安装 sudo apt-get install mysql-server apt-get isntall mysql-client sudo apt-get install libmysqlclient-dev2.设置密码cd /etc/mysql vi debian.cnf ,得…

sqlserver如何定义一个静态变量_[Bazel]自定义规则实现将多个静态库合并为一个动态库或静态库...

1 前言2 自定义规则实现2.1 规则功能2.2 实现规则的理论基础2.3 规则代码实现3 总结4 参考资料1 前言为了实现如标题所述的将多个静态库合并为一个动态库&#xff0c;内置的 Bazel 规则是没有这个功能的&#xff0c;Bazel C/C 相关的内置规则有&#xff1a;cc_binary &#xff…

(转)base64编码(严格说来,base64不算作加解密算法)

【README】 1.本文转自&#xff1a; Java base64加密解密 - xuwc - 博客园参考&#xff1a; https://www.cnblogs.com/luguo3000/p/3940197.html https://blog.csdn.net/jiahao1186/article/detailhttps://www.cnblogs.com/xuwc/p/14058183.htmlhttps://zh.wikipedia.org/wik…

JavaWeb的web.xml标签元素(一)

普通信息标签 &#xff08;1&#xff09;display-name 定义了WEB应用的名字,在servletContextConfig display-name* XML语法&#xff1a;<display-name>AppName</display-name> &#xff08;2&#xff09;description 可以使用description元素来提供有关部署描述符…

python3多线程第三方库_Python3 多线程

Python3 多线程多线程类似于同时执行多个不同程序&#xff0c;多线程运行有如下优点&#xff1a;使用线程可以把占据长时间的程序中的任务放到后台去处理。用户界面可以更加吸引人&#xff0c;比如用户点击了一个按钮去触发某些事件的处理&#xff0c;可以弹出一个进度条来显示…

采用MiniProfiler监控EF与.NET MVC项目

今天来说说EF与MVC项目的性能检测和监控&#xff0c;相对本篇文章&#xff0c;更推荐大家使用另一个类似组件 NanoProfiler - 适合生产环境的性能监控类库 之 基本功能篇 首先,先介绍一下今天我们使用的工具吧. MiniProfiler~ 这个东西的介绍如下: MVC MiniProfiler是Stack Ove…

(转)公钥,私钥和数字签名这样最好理解

转自&#xff1a; 公钥&#xff0c;私钥和数字签名这样最好理解_21aspnet的博客-CSDN博客_公钥签名还是私钥签名一、公钥加密 假设一下&#xff0c;我找了两个数字&#xff0c;一个是1&#xff0c;一个是2。我喜欢2这个数字&#xff0c;就保留起来&#xff0c;不告诉你们(私钥…

JavaWeb的web.xml标签元素(二)

JavaWeb的web.xml标签元素&#xff08;一&#xff09; 九、session-config为Web应用中的javax.servlet.http.HttpSession对象定义参数 session-config-session-timeout?session-timeout元素用来指定默认的会话超时时间间隔&#xff0c;以分钟为单位。该元素值必须为整数。如果…

登录系统 提示框_实物资产管理软件操作手册(职员和系统用户)

一、职员和系统用户“职员和系统用户”菜单提供了对公司人员信息进行维护的功能&#xff0c;可以自定义职员编码、 姓名、用户名等相关职员信息。注意&#xff1a;职员编码、用户名需唯一&#xff0c;且用户名一旦保存则不可修改。根据要求可以将“用户类型”分为“职员”和“系…

HoloLens开发手记-凝视 Gaze

凝视 Gaze 在全息应用中&#xff0c;凝视是第一种输入形式&#xff0c;被用于定位物体&#xff08;功能和PC的光标指针类似&#xff09;。凝视告诉你用户正在看向世界中的位置&#xff0c;让你能够确定他们的意图。在现实世界中&#xff0c;你通常会盯着你打算与之交互的物体。…

摘要算法与加密(以MD5算法为例)

【README】 部分内容总结自&#xff1a; 摘要与加密的区别&#xff08;以MD5算法为例&#xff09; - 掘金https://juejin.cn/post/6844903561478799368 【1】摘要算法与加密区别 【1.1】摘要算法&#xff08;不可逆&#xff09; 1&#xff09;摘要算法&#xff1a; 说白了…

Oracle入门(三A)之sqlplus

转载自 sqlplus /nolog是什么意思sqlplus命令格式如下&#xff1a; 用法: SQLPLUS [ [<option>] [<logon>] [<start>] ] 其中 <option> :: -H | -V | [ [-M <o>] [-R <n>] [-S] ] <登录> :: <用户名>[/<口令>][<con…

.Net Core下如何管理配置文件

一、前言 根据该issues来看&#xff0c;System.Configuration在.net core中已经不存在了&#xff0c;那么取而代之的是由Microsoft.Extensions.Cnfiguration.XXX一系列的类库提供&#xff0c;对应的开源地址为点击这里。 从当前开源的代码来看&#xff0c;在.net core下提供了…

python短视频自动制作_Python 带你一键生成朋友圈超火的九宫格短视频

1. 场景如果你经常刷抖音和微信朋友圈&#xff0c;一定发现了最近九宫格短视频很火&#xff01;​从朋友圈九宫格图片&#xff0c;到九宫格视频&#xff0c;相比传统的图片视频&#xff0c;前者似乎更有个性和逼格除了传统的剪辑软件可以实现&#xff0c;是否有其他更加快捷方便…

3-系统总线

【README】 1.本文总结自B站 《计算机组成原理&#xff08;哈工大刘宏伟&#xff09;》的视频讲解&#xff0c;非常棒&#xff0c;墙裂推荐&#xff1b; 2.补充&#xff1a;冯洛伊曼计算机由5大部分组成&#xff1a; 1. 运算器2. 控制器3. 存储器4. 输入设备5. …

HashMap中傻傻分不清楚的那些概念

转载自 HashMap中傻傻分不清楚的那些概念 很多人在通过阅读源码的方式学习Java&#xff0c;这是个很好的方式。而JDK的源码自然是首选。在JDK的众多类中&#xff0c;我觉得HashMap及其相关的类是设计的比较好的。很多人读过HashMap的代码&#xff0c;不知道你们有没有和我一样&…

HoloLens开发手记-硬件细节 Hardware Detail

微软HoloLens是世界第一款完全无线缆的全息计算机。通过在新方式上赋予用户的全息体验&#xff0c;HoloLens重新定义了个人计算&#xff08;Personal Computing&#xff09;。为了将3D全息图形固定到你周围的真实世界中&#xff0c;HoloLens融合了最先进的光学元件和传感器。 设…

super构造方法为什么给子类赋值_【Java学习 | Javase】super

整理自&#xff1a;动力节点基础讲义super概述 严格来说&#xff0c;super其实并不是一个引用&#xff0c;它只是一个关键字&#xff0c;super代表了当前对象中从父类继承过来的那部分特征。换句话说&#xff0c;super其实是this的一部分&#xff0c;从父类继承过来的属性和方法…

4-存储器

【README】 1.本文总结自B站 《计算机组成原理&#xff08;哈工大刘宏伟&#xff09;》的视频讲解&#xff0c;非常棒&#xff0c;墙裂推荐&#xff1b; 【1】概述 【1.1】存储器分类 1&#xff09;按存储介质分类 1&#xff0c; 半导体存储器&#xff0c;分为 TTL&#…

坑爹的日志无法按天切割问题

转载自 坑爹的日志无法按天切割问题问题背景 线上某个新管理型系统出现了日志无法按天切割生成日志文件的问题&#xff0c;所有的日志都在一个日志文件里面&#xff0c;只有每次重启的时候才会重新生成文件。 这个管理系统使用的是 Spring Boot Logback 框架&#xff0c;查看了…