提到“配置”二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置信息定义在这两个文件之中。到了.NET Core的时候,很多我们习以为常的东西都发生了改变,其中也包括定义配置的方式。总的来说,新的配置系统显得更加轻量级,并且具有更好的扩展性,其大的特点是支持多样化的数据源。我们可以采用内存的变量作为配置的数据源,也可以直接配置定义在持久化的文件甚至数据库中。
  由于很多人都不曾接触过这个采用全新设计的配置系统,为了让大家对此有一个感官的认识,我们先从编程的角度对它作一个初体验。针对配置的API涉及三个对象,它们分别是Configuration、ConfigurationBuilder和ConfigurationProvider,配置模型中具有相应的接口来表示它们。这三个对象之间的关系很清晰,Configuration对象承载着在编程过程中使用的配置信息,ConfigurationProvider则是配置信息原始数据源的提供者,两者之间沟通由ConfigurationBuilder来完成,它利用ConfigurationProvider提取源数据将其转换为Configuration对象。
  一、以键-值对的形式读取配置
  虽然在大部分情况下的配置信息从整体来说都具有一个结构化的层次关系,但是“原子”配置项都以简单的“键-值对”的形式来体现,并且键和值都是字符串,接下来我们会通过一个简单的实例来演示如何以键值对的形式来读取配置。我们创建一个针对ASP.NET Core的控制台应用,并在project.json中按照如下的方式添加针对“Microsoft.Extensions.Configuration”这个NuGet包的依赖,配置模型实现在这个包中。
  1: {
  2:   ...
  3:   "dependencies": {
  4:     "Microsoft.Extensions.Configuration": "1.0.0-rc1-final"
  5:   },
  6: }
  假设我们的应用需要通过配置来设定日期/时间的显示格式,为此我们定义了如下一个DateTimeFormatSettings类,它的四个属性体现了DateTime对象的四种显示格式(分别为长日期/时间和短日期/时间)。
  1: public class DateTimeFormatSettings
  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: }
  我们希望通过配置的形式来控制由DateTimeFormatSettings的四个属性体现的日期/时间显示格式,所以我们为它定义了一个构造函数。如下面的代码片段所示,该构造函数具有一个IConfiguration接口类型的参数,它正式承载相关配置信息的Configuration对象。我们调用Configuration对象的索引并指定相应配置项的Key来得到其Value。
  1: public class DateTimeFormatSettings
  2: {
  3:     //其他成员
  4:     public DateTimeFormatSettings (IConfiguration configuration)
  5:     {
  6:         this.LongDatePattern     = configuration["LongDatePattern"];
  7:         this.LongTimePattern     = configuration["LongTimePattern"];
  8:         this.ShortDatePattern    = configuration["ShortDatePattern"];
  9:         this.ShortTimePattern    = configuration["ShortTimePattern"];
  10:     }
  11: }
  要创建一个体现当前配置的DateTimeFormatSettings对象,我们必须向得到这个承载相关配置信息的Configuration对象。正如我们上面所说,Configuration对象是由ConfigurationBuilder创建的,而原始的配置信息则是通过相应的ConfigurationProvider来读取的,所以创建一个Configuration对象的正确编程方式是先创建一个ConfigurationBuilder对象,然后为之添加一个或者多个ConfigurationProvider对象,后利用ConfigurationBuilder来创建我们需要的Configuration对象。
  按照上述的编程模式,我们在一个控制台应用中编写了如下的程序。我们创建了一个类型为ConfigurationBuilder的对象,调用其Add方法添加的ConfigurationProvider是一个类型为MemoryConfigurationProvider的对象。顾名思义,MemoryConfigurationProvider利用内存中的对象来提供原始的配置信息,具体来说这些原始的配置信息保存在一个元素类型为KeyValuePair<string, string>的集合之中。我们终调用ConfigurationBuilder的Build方法获取用于创建DateTimeFormatSettings对象所需的Configuration。
1: public class Program
2: {
3:     public static void Main(string[] args)
4:     {
5:         Dictionary<string, string> source = new Dictionary<string, string>
6:         {
7:             ["LongDatePattern"]     = "dddd, MMMM d, yyyy",
8:             ["LongTimePattern"]     = "h:mm:ss tt",
9:             ["ShortDatePattern"]    = "M/d/yyyy",
10:             ["ShortTimePattern"]    = "h:mm tt"
11:         };
12:         IConfiguration configuration = new ConfigurationBuilder()
13:                 .Add(new MemoryConfigurationProvider(source))
14:                 .Build();
15:
16:         DateTimeFormatSettings settings = new DateTimeFormatSettings(configuration);
17:         Console.WriteLine("{0,-16}: {1}", "LongDatePattern", settings.LongDatePattern);
18:         Console.WriteLine("{0,-16}: {1}", "LongTimePattern", settings.LongTimePattern);
19:         Console.WriteLine("{0,-16}: {1}", "ShortDatePattern", settings.ShortDatePattern);
20:         Console.WriteLine("{0,-16}: {1}", "ShortTimePattern", settings.ShortTimePattern);
21:     }
22: }
  为了验证根据配置创建的DateTimeFormatSettings对象与配置原始数据之间的关系,我们将它的四个属性输出于控制台上。当这个程序执行之后将在控制台上产生如下所示的输出,可以看出它正是我们提供的配置的真实反映。
  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对象来体现。体现为键值对的原子配置项一般至存在于作为叶子节点的Configuration对象中,非叶子节点的Configuration包含一组子节点,而每个子节点同样是一个Configuration对象。
  接下来我们同样以实例的方式来演示如何定义并读取具有层次化结构的配置。我们依然沿用上一节的应用场景,现在我们不仅仅需要设置日期/时间的格式,还需要设置其他数据类型的格式,比如表示货币的Decimal类型。为此我们定义了如下一个CurrencyDecimalFormatSettings类,它的属性Digits和Symbol分别表示小数位数和货币符号,一个CurrencyDecimalFormatSettings对象依然是利用一个表示配置的Configuration对象来创建的。
  1: {
  2:     public int     Digits { get; set; }
  3:     public string  Symbol { get; set; }
  4:
  5:     public CurrencyDecimalFormatSettings(IConfiguration configuration)
  6:     {
  7:         this.Digits = int.Parse(configuration["Digits"]);
  8:         this.Symbol = configuration["Symbol"];
  9:     }
  10: }