提到“配置”二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义在这两个文件之中。到了.NET Core的时代,很多我们习以为常的东西都发生了改变,其中也包括定义配置的方式。总的来说,新的配置系统显得更加轻量级,并且具有更好的扩展性,其最大的特点就是支持多样化的数据源。我们可以采用内存的变量作为配置的数据源,也可以直接配置定义在持久化的文件甚至数据库中。由于很多人都不曾接触过这个采用全新设计的配置系统,为了让大家对它有一个大体的认识,我们先从编程的角度来体验一下全新的配置读取方式。这个全新的配置系统为配置的读取定义了非常简单的API,这些API涉及到三个核心的对象,我们不妨称之为“配置编程模型三要素”。 [ 本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、配置编程模型三要素
二、以键-值对的形式读取配置
三、读取结构化的配置
四、将结构化配置直接绑定为对象

一、配置编程模型三要素

就编程层面来讲,.NET Core的这个配置系统由如下图所示的三个核心对象构成。读取出来的配置信息最终会转换成一个Configuration对象供我们的程序使用。ConfigurationBuilder是Configuration对象的构建者,而ConfigurationSource则代表配置最原始的来源。

在读取配置的时候,我们根据配置的定义方式创建相应的ConfigurationSource,并将其注册到创建的ConfigurationBuilder对象上。由于提供配置的最初来源可能不止一个,所以我们可以注册多个相同或者不同类型的ConfigurationSource对象到ConfigurationBuilder上。ConfigurationBuilder这是利用注册的这些ConfigurationSource提供的原始数据最终构建出我们在程序中使用的Configuration对象。

根据本系列文章一贯采用的命名方式,我们应该知道上面介绍的Configuration、ConfigurationSource和ConfigurationBuilder均是对一类对象的统称,它们在API层面都通过相应的接口(IConfiguration、IConfigurationSource和IConfigurationBuilder)来表示,这些接口均义在NuGet包“Microsoft.Extensions.Configuration.Abstractions”中。如果我们的程序中只需要使用到这些接口,我们只需要添加针对这个NuGet包的依赖。至于这些接口的默认实现类型,则大多定义在“Microsoft.Extensions.Configuration”这个NuGet包中。

二、以键-值对的形式读取配置

虽然在大部分情况下的配置从整体来说都具有结构化的次关系,但是“原子”配置项都以最简单的“键-值对”的形式来体现,并且键和值通常都是字符串,接下来我们会通过一个简单的实例来演示如何以键值对的形式来读取配置。我们创建一个针对ASP.NET Core的控制台应用,并在project.json中按照如下的方式添加针对“Microsoft.Extensions.Configuration”这个NuGet包的依赖,配置模型就实现在这个包中。

   1: {

   2:   ...

   3:   "dependencies": {

   4:     "Microsoft.Extensions.Configuration": "1.0.0 "

   5:   },

   6: }

假设我们的应用程序需要通过配置来设定日期/时间的显示格式,为此我们将相关的配置信息定义在如下所示的这个DateTimeFormatOptions类,它的四个属性体现针对DateTime对象的四种显示格式(分别为长日期/时间和短日期/时间)。

   1: public class DateTimeFormatOptions

   2: {

   3:     public string LongDatePattern { get; set; }

   4:     public string LongTimePattern { get; set; }

   5:     public string ShortDatePattern { get; set; }

   6:     public string ShortTimePattern { get; set; }

   7:     //其他成员

   8: }

我们希望通过配置的形式来控制由DateTimeFormatOptions的四个属性体现的日期/时间显示格式,所以我们为它定义了一个构造函数。如下面的代码片段所示,该构造函数具有一个IConfiguration接口类型的参数,通过上面的介绍我们知道它是配置在应用程序中体现。键值对是配置的基本表现形式,所以Configuration对象提供了索引使我们可以根据配置项的Key得到配置项的值,下面的代码正式调用索引的方式得到对应配置信息的。

   1: public class DateTimeFormatOptions

   2: {

   3:     //其他成员

   4:     public DateTimeFormatOptions (IConfiguration config)

   5:     {

   6:         this.LongDatePattern     = config ["LongDatePattern"];

   7:         this.LongTimePattern     = config ["LongTimePattern"];

   8:         this.ShortDatePattern    = config ["ShortDatePattern"];

   9:         this.ShortTimePattern    = config ["ShortTimePattern"];

  10:     }

  11: }

要创建一个体现当前配置的DateTimeFormatOptions对象,我们必须向得到这个承载相关配置信息的Configuration对象。正如我们上面所说,Configuration对象是由ConfigurationBuilder创建的,而原始的配置信息则是通过相应的ConfigurationSource来提取的,所以创建一个Configuration对象的正确编程方式是先创建一个ConfigurationBuilder对象,然后为之注册一个或者多个ConfigurationSource对象,最后利用ConfigurationBuilder来创建我们需要的Configuration对象。

我们通过如下的程序来读取配置并将其转换成一个DateTimeFormatOptions对象。简单起见,我们采用一中类型为MemoryConfigurationSource的ConfigurationSource,它直接利用一个保存在内存中的字典对象作为最初的配置来源。如下面的代码片段所示,我们在为MemoryConfigurationSource提供的字典对象中设置了四种类型的日期/时间显示格式。

   1: Dictionary<string, string> source = new Dictionary<string, string>

   2: {

   3:     ["longDatePattern"] = "dddd, MMMM d, yyyy",

   4:     ["longTimePattern"] = "h:mm:ss tt",

   5:     ["shortDatePattern"] = "M/d/yyyy",

   6:     ["shortTimePattern"] = "h:mm tt"

   7: };

   8:  

   9: IConfiguration config = new ConfigurationBuilder()

  10:     .Add(new MemoryConfigurationSource { InitialData = source })

  11:     .Build();

  12:  

  13: DateTimeFormatOptions options = new DateTimeFormatOptions(config);

  14: Console.WriteLine($"LongDatePattern: {options.LongDatePattern}");

  15: Console.WriteLine($"LongTimePattern: {options.LongTimePattern}");

  16: Console.WriteLine($"ShortDatePattern: {options.ShortDatePattern}");

  17: Console.WriteLine($"ShortTimePattern: {options.ShortTimePattern}");

我们创建了一个ConfigurationBuilder类型的对象,并将这个MemoryConfigurationSource注册到它上面。接下来,我们直接调用ConfigurationBuilder的Build方法创建出Configuration对象,并利用后者创建了一个DateTimeFormatOptions对象。为了验证DateTimeFormatOptions对象是否与原始的配置一致,我们将它的四个属性打印在控制台上。程序运行之后,控制台上将会产生如下所示的输出结果。

   1: LongDatePattern : dddd, MMMM d, yyyy

   2: LongTimePattern : h:mm:ss tt

   3: ShortDatePattern: M/d/yyyy

   4: ShortTimePattern: h:mm tt

三、读取结构化的配置

真实项目中涉及的配置大都具有结构化的层次结构,所以Configuration对象同样具有这样的结构。结构化配置具有一个树形层次结构,我们不妨将其称之为“配置树”,一个Configuration对象最终对应着这棵配置树的某个节点,而整棵配置树自然可以由根节点对应的Configuration对象来表示。以键值对体现的“原子配置项”一般对应于配置树中不具有子节点的“叶子节点”。

接下来我们同样以实例的方式来演示如何定义并读取具有层次结构的配置。我们依然沿用上一节的应用场景,不过现在我们不仅仅需要设置日期/时间的格式,还需要设置其他数据类型的格式,比如表示货币的Decimal类型。为此我们定义了如下一个CurrencyDecimalFormatOptions类,它的属性Digits和Symbol分别表示小数位数和货币符号,一个CurrencyDecimalFormatOptions对象依然是利用一个Configuration对象来创建的。

   1: public class CurrencyDecimalFormatOptions

   2: {

   3:     public int        Digits { get; set; }

   4:     public string     Symbol { get; set; }

   5:  

   6:     public CurrencyDecimalFormatOptions (IConfiguration config)

   7:     {

   8:         this.Digits = int.Parse(config["Digits"]);

   9:         this.Symbol = config["Symbol"];

  10:     }

  11: }

我们定义了另一个名为FormatOptions的类型来表示针对不同数据类型的格式设置。如下面的代码片段所示,它的两个属性DateTime和CurrencyDecimal分别表示针对日期/时间和货币数字的格式设置。FormatOptions依然具有一个参数类型为IConfiguration接口的构造函数,它的两个属性均在此构造函数中被初始化。值得注意的是初始化这两个属性采用的是当前Configuration的“子配置节”,我们通过指定配置节名称调用GetSection方法获得这两个子配置节。

   1: public class FormatOptions

   2: {

   3:     public DateTimeFormatOptions            DateTime { get; set; }

   4:     public CurrencyDecimalFormatOptions     CurrencyDecimal { get; set; }

   5:  

   6:     public FormatOptions (IConfiguration config)

   7:     {

   8:         this.DateTime = new DateTimeFormatOptions (config.GetSection("DateTime"));

   9:         this.CurrencyDecimal = new CurrencyDecimalFormatOptions (config.GetSection("CurrencyDecimal"));

  10:     }

  11: }

FormatOptions类型体现的配置具有如下图所示的树形层次化结构。在我们上面演示的实例中,我们通过以一个MemoryConfigurationSource对象来提供原始的配置信息。由于承载原始配置信息的是一个元素类型为KeyValuePair<string, string>的集合,它在物理存储上并不具有树形化的层次结构,那么它如何能够最终提供一个结构化的Configuration对象呢?

解决方案其实很简单,对于一棵完整的配置树,具体的配置信息最终是通过叶子节点来承载的,所以MemoryConfigurationSource只需要在配置字典中保存叶子节点的数据即可。除此之外,为了描述配置树的结构,配置字典需要将对应叶子节点在配置树种的路径作为Key。所以MemoryConfigurationSource可以采用下表所示的配置字典对配置数进行“扁平化”,路径采用冒号(“:”)作为分隔符。

Key

Value

Format:DateTime:LongDatePattern

dddd, MMMM d, yyyy

Format:DateTime:LongTimePattern

h:mm:ss tt

Format:DateTime:ShortDatePattern

M/d/yyyy

Format:DateTime:ShortTimePattern

h:mm tt

Format:CurrencyDecimal:Digits

2

Format:CurrencyDecimal:Symbol

$

如下面的代码片段所示,我们按照表1所示的结构创建了一个Dictionary<string, string>对象,并利用它创建出MemoryConfigurationSource对象。在利用ConfigurationBuildr得到表示整个配置的Configuration对象之后,我们调用其GetSection方法得到名称为“Format”的配置节,并利用后者创建一个FormatOptions。

   1: Dictionary<string, string> source = new Dictionary<string, string>

   2: {

   3:     ["format:dateTime:longDatePattern"] = "dddd, MMMM d, yyyy",

   4:     ["format:dateTime:longTimePattern"] = "h:mm:ss tt",

   5:     ["format:dateTime:shortDatePattern"] = "M/d/yyyy",

   6:     ["format:dateTime:shortTimePattern"] = "h:mm tt",

   7:  

   8:     ["format:currencyDecimal:digits"] = "2",

   9:     ["format:currencyDecimal:symbol"] = "$",

  10: };

  11: IConfiguration configuration = new ConfigurationBuilder()

  12:         .Add(new MemoryConfigurationSource { InitialData = source })

  13:         .Build();

  14:  

  15: FormatOptions options = new FormatOptions(configuration.GetSection("Format"));

  16: DateTimeFormatOptions dateTime = options.DateTime;

  17: CurrencyDecimalFormatOptions currencyDecimal = options.CurrencyDecimal;

  18:  

  19: Console.WriteLine("DateTime:");

  20: Console.WriteLine($"\tLongDatePattern: {dateTime.LongDatePattern}");

  21: Console.WriteLine($"\tLongTimePattern: {dateTime.LongTimePattern}");

  22: Console.WriteLine($"\tShortDatePattern: {dateTime.ShortDatePattern}");

  23: Console.WriteLine($"\tShortTimePattern: {dateTime.ShortTimePattern}");        

  24:  

  25: Console.WriteLine("CurrencyDecimal:");

  26: Console.WriteLine($"\tDigits:{currencyDecimal.Digits}");

  27: Console.WriteLine($"\tSymbol:{currencyDecimal.Symbol}");

在得到利用读取的配置创建的 FormatOptions对象之后,为了验证该对象与原始配置数据是否一致,我们依然将它的相关属性打印在控制台上。这个程序之后之后改程序会在控制台上呈现如下所示的输出结果。(S02)

   1: DateTime:

   2:         LongDatePattern : dddd, MMMM d, yyyy

   3:         LongTimePattern : h:mm:ss tt

   4:         ShortDatePattern: M/d/yyyy

   5:         ShortTimePattern: h:mm tt

   6:  

   7: CurrencyDecimal:

   8:         Digits          : 2

   9:         Symbol          : $

四、将结构化配置直接绑定为对象

在真正的项目开发过程中,我们倾向于像我们演示的两个实例一样通过创建相应的类型(比如演示实例中的DateTimeFormatOptions、CurrencyDecimalOptions和FormatOptions)来定义一组相关的配置选项(Option),我们将定义配置选项(Option)的这些类型称为Option类型。在上面演示的实例中,为了创建这些封装配置的对象,我们都是采用手工读取配置的形式,如果定义的配置项太多的话,逐条读取配置项其实是一项非常繁琐的工作。

对于一个对象来说,如果我们将它的属性视为它的子节点,一个对象同样具有类似于Configuration对象的树形层次结构。如果我们根据某个Option类型的结构来定义配置,或者根据配置的结构来定义这个Option类型,Option类型的属性成员将与某个配置节具有一一对应的关系,那么原则上我们可以自动将配置信息绑定为一个具体的Option对象。

.NET Core的配置系统采用一种叫做“Options Pattern”的编程模式来支持从原始配置到Options对象之间的绑定。这种编程模式涉及的API定义在“Microsoft.Extensions.Options.ConfigurationExtensions”这个NuGet包中,所以我们需要在project.json文件中按照如下的方式添加针对性的依赖。除此之外,“Options Pattern”涉及到对DI的使用,所以我们还需要添加针对NuGet包“Microsoft.Extensions
.DependencyInjection”的依赖。

   1: {

   2:   ...

   3:   "dependencies": {

   4:     "Microsoft.Extensions.Options.ConfigurationExtensions"    : "1.0.0",

   5:     "Microsoft.Extensions.DependencyInjection"                : "1.0.0"

   6:   },

   7: }

借助于Options Pattern的自动绑定机制,我们无需逐条地读取配置,所以我们可以将这个三个Options类型(DateTimeFormatOptions、CurrencyDecimalOptions和FormatOptions)的构造函数全部删除,只保留其属性成员。在作为程序入口的Main方法中,我们采用如下的方式创建这个表示格式设置的FormatOptions对象。

   1: ...

   2: FormatOptions options = new ServiceCollection()

   3:     .AddOptions()

   4:     .Configure<FormatOptions>(config.GetSection("Format"))

   5:     .BuildServiceProvider()

   6:     .GetService<IOptions<FormatOptions>>()

   7:     .Value;

如上面的代码片段所示,我们创建一个ServiceCollection对象并调用扩展方法AddOptions注册于针对Option模型的服务。接下来我们调用Configure方法将FormatOptions这个Option类型与对应的Configuration对象进行映射。我们最后利用这个ServiceCollection对象生成一个ServiceProvider,并调用其GetService方法得到一个类型为IOptions<FormatOptions>的对象,后者的Value属性返回的就是绑定了相关配置的FormatOptions对象。

.NET Core采用的全新配置系统[1]: 读取配置数据的更多相关文章

  1. .NET Core采用的全新配置系统[10]: 配置的同步机制是如何实现的?

    配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.要了解配置同步机制的实现原理,先得从认识一个 ...

  2. .NET Core采用的全新配置系统[2]: 配置模型设计详解

    在<.NET Core采用的全新配置系统[1]: 读取配置数据>中,我们通过实例的方式演示了几种典型的配置读取方式,其主要目的在于使读者朋友们从编程的角度对.NET Core的这个全新的配 ...

  3. .NET Core采用的全新配置系统[3]: “Options模式”下的配置是如何绑定为Options对象

    配置的原子结构就是单纯的键值对,并且键和值都是字符串,但是在真正的项目开发中我们一般不会单纯地以键值对的形式来使用配置.值得推荐的做法就是采用<.NET Core采用的全新配置系统[1]: 读取 ...

  4. .NET Core采用的全新配置系统[7]: 将配置保存在数据库中

    我们在<聊聊默认支持的各种配置源>和<深入了解三种针对文件(JSON.XML与INI)的配置源>对配置模型中默认提供的各种ConfigurationSource进行了深入详尽的 ...

  5. [ASP.NET Core 3框架揭秘] 配置[1]:读取配置数据[上篇]

    提到"配置"二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化 ...

  6. [ASP.NET Core 3框架揭秘] 配置[2]:读取配置数据[下篇]

    [接上篇]提到“配置”二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义 ...

  7. .NET Core采用的全新配置系统[5]: 聊聊默认支持的各种配置源[内存变量,环境变量和命令行参数]

    较之传统通过App.config和Web.config这两个XML文件承载的配置系统,.NET Core采用的这个全新的配置模型的最大一个优势就是针对多种不同配置源的支持.我们可以将内存变量.命令行参 ...

  8. .NET Core采用的全新配置系统[9]: 为什么针对XML的支持不够好?如何改进?

    物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigura ...

  9. .NET Core采用的全新配置系统[8]: 如何实现配置与源文件的同步

    配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.接下来我们利用一个简单的.NET Core控 ...

随机推荐

  1. 【AR实验室】ARToolKit之制作自己的Marker/NFT

    0x00 - 前言 看过example后,就会想自己动动手,这里改改那里修修.我们先试着添加自己喜欢的marker/nft进行识别. 比如我做了一个法拉利的marker: 还有网上找了一个法拉利log ...

  2. JS核心系列:浅谈 call apply 与 bind

    在JavaScript 中,call.apply 和 bind 是 Function 对象自带的三个方法,这三个方法的主要作用是改变函数中的 this 指向,从而可以达到`接花移木`的效果.本文将对这 ...

  3. 史上最详细git教程

    题外话 虽然这个标题很惊悚,不过还是把你骗进来了,哈哈-各位看官不要着急,耐心往下看 Git是什么 Git是目前世界上最先进的分布式版本控制系统. SVN与Git的最主要的区别 SVN是集中式版本控制 ...

  4. virtualbox linux虚拟机相关

    linux虚拟机设置为静态IP 在virtualbox中安装好linux虚拟机后,如果采用的是NAT方式的话,linux虚拟机默认采用dhcp方式自动上网,而且用的是NetworkManager服务而 ...

  5. Android学习路线总结,绝对干货

    title: Android学习路线总结,绝对干货 tags: Android学习路线,Android学习资料,怎么学习android grammar_cjkRuby: true --- 一.前言 不 ...

  6. Hibernate 系列 学习笔记 目录 (持续更新...)

    前言: 最近也在学习Hibernate,遇到的问题差不多都解决了,顺便把学习过程遇到的问题和查找的资料文档都整理了一下分享出来,也算是能帮助更多的朋友们了. 最开始使用的是经典的MyEclipse,后 ...

  7. linux之查看系统命令

    cpu信息 1.查看逻辑cpu核数 # cat /proc/cpuinfo| grep "processor"| wc -l 2.查看物理cpu个数 # cat /proc/cpu ...

  8. OpenGL shader 中关于顶点坐标值的思考

    今天工作中需要做一个事情: 在shader内部做一些空间距离上的计算,而且需要对所有的点进行计算,符合条件的显示,不符合条件的点不显示. 思路很简单,在vertex shader内知道顶点坐标,进行计 ...

  9. oracle SEQUENCE 创建, 修改,删除

    oracle创建序列化: CREATE SEQUENCE seq_itv_collection            INCREMENT BY 1  -- 每次加几个              STA ...

  10. ASP.NET Core 缓存技术 及 Nginx 缓存配置

    前言 在Asp.Net Core Nginx部署一文中,主要是讲述的如何利用Nginx来实现应用程序的部署,使用Nginx来部署主要有两大好处,第一是利用Nginx的负载均衡功能,第二是使用Nginx ...