你的位置:首页 > ASP.net教程

[ASP.net教程].NET Core采用的全新配置系统[6]: 深入了解三种针对文件(JSON、XML与INI)的配置源


物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON、

目录
一、FileConfigurationSource  & FileConfigurationProvider
二、JsonConfigurationSource &JsonConfigurationProvider
三、四、IniConfigurationSource & IniConfigurationSource

一、FileConfigurationSource  & FileConfigurationProvider

上述这三个具体的ConfigurationSource类型具有如下一个相同的基类FileConfigurationSource。

  1: public abstract class FileConfigurationSource : IConfigurationSource
  2: {
  3:   public IFileProvider   FileProvider { get; set; }
  4:   public bool       Optional { get; set; }
  5:   public string      Path { get; set; }
  6:   public bool       ReloadOnChange { get; set; }
  7:  
  8:   public abstract IConfigurationProvider Build(IConfigurationBuilder builder);
  9: }


如上面的代码片段所示,FileConfigurationSource是一个抽象类,它利用一个FileProvider对象来提供原始的配置文件,而Path属性自然代表配置文件的路径。Optional属性表示明当前的FileConfigurationSource是否是可选的配置源,其默认值为False。当某个FileConfigurationSource的Optional属性为True的时候,如果指定的配置文件路径不存在,将不会有任何异常被抛出来。由于FileProvider具有监控文件变化的能力,它的ReloadOnChange属性表示如果被监控的配置文件发生改变后是否需要重新加载配置。

由于FileConfigurationSource总是利用FileProvider来读取配置文件的内容,所以当我们创建一个具体的FileConfigurationSource对象的时候都需要采用显式或者隐式的方式指定一个FileProvider对象(关于FileProvider,可以参阅我的“文件系统”博文系列)。我们可以调用扩展方法SetFileProvider将一个默认的FileProvider注册到ConfigurationBuilder对象上,从相面的代码片段可以看出注册的FileProvider被保存到Properties属性表示的字典对象上,对应的Key为“FileProvider”。

  1: public static class FileConfigurationExtensions
  2: {
  3:   private static string FileProviderKey = "FileProvider";
  4:  
  5:   public static IFileProvider GetFileProvider(this IConfigurationBuilder builder)
  6:   {
  7:     object obj2;    
  8:     if (builder.Properties.TryGetValue(FileProviderKey, out obj2))
  9:     {
 10:       return (builder.Properties[FileProviderKey] as IFileProvider);
 11:     }
 12:     return new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty);
 13:   }
 14:  
 15:   public static IConfigurationBuilder SetBasePath(this IConfigurationBuilder builder, string basePath)
 16:   {    
 17:     return builder.SetFileProvider(new PhysicalFileProvider(basePath));
 18:   }
 19:  
 20:   public static IConfigurationBuilder SetFileProvider(this IConfigurationBuilder builder, IFileProvider fileProvider)
 21:   {    
 22:     builder.Properties[FileProviderKey] = fileProvider;
 23:     return builder;
 24:   }
 25: }


通过隐式方式提供的FileProvider通过调用ConfigurationBuilder的另一个扩展方法GetFileProvider方法获取。从上面给出的代码片段我们可以看到,它会优先返回我们注册的FileProvider。如果这样的FileProvdier尚未注册,该方法会返回指向当前应用执行目录的PhysicalFileProvider对象。除了上述这两个方法,ConfigurationBuilder还具有另一个名为SetBasePath的方法,该方法采用指定的路径创建一个PhysicalFileProvider对象并对它进行注册。

虽然JsonConfigurationSource、.FileExtensions”这个NuGet包中。

  1: public abstract class FileConfigurationProvider : ConfigurationProvider
  2: {
  3:  
  4:   public FileConfigurationSource Source { get; private set; }
  5:  
  6:   public FileConfigurationProvider(FileConfigurationSource source)
  7:   {
  8:     this.Source = source;
  9:     if (this.Source.ReloadOnChange)
 10:     {
 11:       ChangeToken.OnChange(
 12:         () => this.Source.FileProvider.Watch(this.Source.Path), 
 13:         this.Load);
 14:     }
 15:   }
 16:  
 17:   public override void Load()
 18:   {
 19:     IFileInfo fileInfo = this.Source.FileProvider.GetFileInfo(this.Source.Path);
 20:  
 21:     if ((fileInfo == null) || !fileInfo.Exists)
 22:     {
 23:       if (!this.Source.Optional)
 24:       {
 25:         throw new FileNotFoundException();
 26:       }
 27:       base.Data = new Dictionary<string, string>(
 28:         StringComparer.OrdinalIgnoreCase);
 29:     }
 30:     else
 31:     {
 32:       using (Stream stream = fileInfo.CreateReadStream())
 33:       {
 34:         this.Load(stream);
 35:       }
 36:     }
 37:     base.OnReload();
 38:   }
 39:  
 40:   public abstract void Load(Stream stream);
 41: }


我们通过如上所示的代码片段以简化的形式模拟了FileConfigurationProvider的实现逻辑。它定义了一个抽象方法Load来完成针对配置文件的读取和配置字典的生成,该参数代表读取文件的输出流。在重写的Load方法中,它直接利用FileProvider得到描述配置文件的FileInfo对象,并调用此FileInfo对象的CreateReadStream方法得到这个Stream对象。

上面的这个代码片段还体现了额外一些细节。首先,如果我们将FileConfigurationSource的ReloadOnChange属性设置为True,意味着我们希望当配置文件发生该表的时候重新加载该文件。FileConfigurationProvider直接利用FileProvider的Watch方法监视配置文件的变换,并将Load方法注册为回调从而到达配置数据同步的目的。其次,如果指定的配置文件不存在,并且FileConfigurationSource的Optional属性被设置为True,FileConfigurationProvider是不能抛出FileNotFoundException异常的。

二、JsonConfigurationSource&JsonConfigurationProvider

JsonConfigurationSource代表针对通过JSON文件定义的配置源,该类型定义在NuGet包“Microsoft.Extensions.Configuration.Json”中。如下面的代码片段所示,在重写的Build方法中,如果FileProvider属性没有被显式赋值,它会调用ConfigurationBuilder的扩展方法GetFileProvider得到一个FileProvdier并对该属性赋值。Build方法最终创建并返回的是一个根据自己创建的JsonConfigurationProvider对象。作为FileConfigurationProvider的继承者,JsonConfigurationProvider利用重写的Load方法读取配置文件的内容并将其转换成配置字典。

  1: public class JsonConfigurationSource : FileConfigurationSource
  2: {
  3:   public override IConfigurationProvider Build(IConfigurationBuilder builder)
  4:   {
  5:     base.FileProvider = base.FileProvider ?? builder.GetFileProvider();
  6:     return new JsonConfigurationProvider(this);
  7:   }
  8: }
  9:  
 10: public class JsonConfigurationProvider : FileConfigurationProvider
 11: {
 12:   public JsonConfigurationProvider(JsonConfigurationSource source);
 13:   public override void Load(Stream stream);
 14: }


“Microsoft.Extensions.Configuration.Json”这个NuGet包为我们定义了如下所示的一系列针对IConfigurationBuilder接口的扩展方法AddJsonFile来完成针对JsonConfigurationSource的注册。如果调用第一个AddJsonFile方法重载,我们可以利用指定的Action<JsonConfigurationSource>对象对创建的JsonConfigurationSource进行初始化。至于后续的AddJsonFile方法重载,实际上就是通过相应的参数初始化JsonConfigurationSource的Path、Optional和ReloadOnChange属性罢了。

  1: public static class JsonConfigurationExtensions
  2: {
  3:   public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource);
  4:   public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path);
  5:   public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional);
  6:   public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional,bool reloadOnChange);
  7: }


当使用JSON文件来定义配置的时候,我们会发现不论对于何种数据结构(复杂对象、集合、数组和字典),我们都能通过JSON格式以一种简单而自然的方式来定义它们。同样以前面定义的Profile类型为例,我们可以利用如下所示的三个JSON文件分别定义一个完整的Profile对象、一个Profile对象的集合以及一个Key和Value类型分别为字符串和Profile的字典。

Profile类型

  1: public class Profile
  2: {
  3:   public Gender     Gender { get; set; }
  4:   public int       Age { get; set; }
  5:   public ContactInfo   ContactInfo { get; set; }
  6: }
  7:  
  8: public class ContactInfo
  9: {
 10:   public string EmailAddress { get; set; }
 11:   public string PhoneNo { get; set; }
 12: }
 13:  
 14: public enum Gender
 15: {
 16:   Male,
 17:   Female
 18: }


Profile对象:

  1: {
  2:  "profile": {
  3:   "gender"   : "Male",
  4:   "age"     : "18",
  5:   "contactInfo": {
  6:    "email"      : "foobar@outlook.com",
  7:    "phoneNo"     : "123456789"
  8:   }
  9:  }
 10: }


Profile集合或者数组

  1: {
  2:  "profiles": [
  3:   {
  4:    "gender"   : "Male",
  5:    "age"    : "18",
  6:    "contactInfo": {
  7:     "email"    : "foo@outlook.com",
  8:     "phoneNo"   : "123"
  9:    }
 10:   },
 11:   {
 12:    "gender"  : "Male",
 13:    "age"    : "25",
 14:    "contactInfo": {
 15:     "email"   : "bar@outlook.com",
 16:     "phoneNo"   : "456"
 17:    }
 18:   },
 19:   {
 20:    "gender"  : "Female",
 21:    "age"    : "40",
 22:    "contactInfo": {
 23:     "email"    : "baz@outlook.com",
 24:     "phoneNo"   : "789"
 25:    }
 26:   }
 27:  ]
 28: }


Profile字典

  1: {
  2:  "profiles": {
  3:   "foo": {
  4:    "gender"   : "Male",
  5:    "age"    : "18",
  6:    "contactInfo": {
  7:     "email"   : "foo@outlook.com",
  8:     "phoneNo"  : "123"
  9:    }
 10:   },
 11:   "bar": {
 12:    "gender"   : "Male",
 13:    "age"    : "25",
 14:    "contactInfo": {
 15:     "email"    : "bar@outlook.com",
 16:     "phoneNo"   : "456"
 17:    }
 18:   },
 19:   "baz": {
 20:    "gender": "Female",
 21:    "age"  : "40",
 22:    "contactInfo": {
 23:     "email"   : "baz@outlook.com",
 24:     "phoneNo"  : "789"
 25:    }
 26:   }
 27:  }
 28: }



三、

  1: <Profile>
  2:  <Gender>Male</Gender>
  3:  <Age>18</Age>
  4:  <ContactInfo>
  5:   <EmailAddress >foobar@outlook.com</Email>
  6:   <PhoneNo>123456789</PhoneNo>
  7:  </ContactInfo>
  8: </Profile>


或者

  1: <Profile Gender="Male" Age="18">
  2:  <ContactInfo EmailAddress ="foobar@outlook.com" PhoneNo="123456789"/>
  3: </Profile>


虽然

  1: <Profiles>
  2:  <Profile Gender="Male" Age="18">
  3:   <ContactInfo EmailAddress ="foobar@outlook.com" PhoneNo="123"/>
  4:  </Profile>
  5:  <Profile Gender="Male" Age="25">
  6:   <ContactInfo EmailAddress ="bar@outlook.com" PhoneNo="456"/>
  7:  </Profile>
  8:  <Profile Gender="Male" Age="40">
  9:   <ContactInfo EmailAddress ="baz@outlook.com" PhoneNo="789"/>
 10:  </Profile>
 11: </Profiles>


但是这段

  1: foo:Gender
  2: foo:Age
  3: foo:ContactInfo:EmailAddress
  4: foo:ContactInfo:PhoneNo
  5:  
  6: bar:Gender
  7: bar:Age
  8: bar:ContactInfo:EmailAddress
  9: bar:ContactInfo:PhoneNo
 10:  
 11: baz:Gender
 12: baz:Age
 13: baz:ContactInfo:EmailAddress
 14: baz:ContactInfo:PhoneNo


按照这样的结构,如果我们需要以

  1: <Profiles>
  2:  <Foo Gender="Male" Age="18">
  3:   <ContactInfo EmailAddress ="foobar@outlook.com" PhoneNo="123"/>
  4:  </Foo>
  5:  <Bar Gender="Male" Age="25">
  6:   <ContactInfo EmailAddress ="foobar@outlook.com" PhoneNo="123"/>
  7:  </Bar>
  8:  <Baz Gender="Male" Age="18">
  9:   <ContactInfo EmailAddress ="baz@outlook.com" PhoneNo="789"/>
 10:  </Baz>
 11: </Profiles>


针对

  1: public class 
  2: {
  3:   public override IConfigurationProvider Build(IConfigurationBuilder builder)
  4:   {
  5:     base.FileProvider = base.FileProvider ?? builder.GetFileProvider();
  6:     return new this);
  7:   }
  8: }
  9:  
 10: public class 
 11: {  
 12:   public 
 13:   public override void Load(Stream stream);
 14: }


JsonConfigurationSource的注册可以通过调用针对IConfigurationBuilder的扩展方法AddJsonFile来完成。与之类似,“Microsoft.Extensions.Configuration.

  1: public static class 
  2: {
  3:   public static IConfigurationBuilder Addthis IConfigurationBuilder builder, string path);
  4:   public static IConfigurationBuilder Addthis IConfigurationBuilder builder, string path, bool optional);
  5:   public static IConfigurationBuilder Addthis IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange);
  6:   public static IConfigurationBuilder Addthis IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange);
  7: }



四、IniConfigurationSource & IniConfigurationSource

“INI”是“Initialization”的缩写,INI文件又被称为初始化文件,它是Windows系统普遍使用的配置文件,同时也被一些Linux和Unix系统所支持。INI文件直接以键值对的形式定义配置项,如下所示的代码片段体现了INI文件的基本格式。总的来说,INI文件以单纯的“{Key}={Value}”的形式定义配置项,{Value}可以定义在可选的双引号中(如果值的前后包括空白字符,必须使用双引号,否则会被忽略)。

  1: [Section]
  2: key1=value1
  3: key2 = " value2 "
  4: ; comment
  5: # comment
  6: / comment


除了以“{Key}={Value}”的定义的原子配置项外,我们还可以采用“[{SectionName}]”的形式定义配置节对它们进行分组。中括号(“[]”)同时作为下一个的配置节开始的标志,同时也作为上一个配置结束的标志,所以采用INI文件定义的配置节并不存在层次化的结构,即没有“子配置节”的概念。除此之外,我们可以在INI中定义相应的注释,注释行前置的字符可以采用“;”、“#”或者“/”。

由于INI文件自身就体现为一个数据字典,所以我们可以采用“路径化”的Key来定义最终绑定为复杂对象、集合或者字典的配置数据。如果采用INI文件来定义一个Profile对象的基本信息,我们就可以采用如下定义形式。

  1: Gender             = "Male"
  2: Age              = "18"
  3: ContactInfo:EmailAddress    = "foobar@outlook.com"
  4: ContactInfo:PhoneNo      = "123456789"


由于Profile的配置信息具有两个层次(Profile>ContactInfo),我们可以按照如下的形式将EmailAddress和PhoneNo定义在配置节“ContactInfo”中,这个INI文件和上面是完全等效的。

  1: Gender = "Male"
  2: Age   = "18"
  3:  
  4: [ContactInfo]
  5: EmailAddress = "foobar@outlook.com"
  6: PhoneNo   = "123456789"


针对INI文件类型的配置源类型通过如下所示的IniConfigurationSource来表示,该类型定义在“Microsoft.Extensions.Configuration.Ini”这个NuGet包中。IniConfigurationSource在重写的Build方法中会创建的ConfigurationProvdier类型为IniConfigurationProvider。作为抽象类FileConfigurationProvider的继承者,IniConfigurationProvider利用重写的Load方法完成INI文件内容的读取和配置字典的初始化。

  1: public class IniConfigurationSource : FileConfigurationSource
  2: {
  3:   public override IConfigurationProvider Build(IConfigurationBuilder builder)
  4:   {
  5:     base.FileProvider = base.FileProvider ?? builder.GetFileProvider();
  6:     return new IniConfigurationProvider(this);
  7:   }
  8: }
  9:  
 10: public class IniConfigurationProvider : FileConfigurationProvider
 11: {
 12:   public IniConfigurationProvider(IniConfigurationSource source);
 13:   public override void Load(Stream stream);
 14: }


既然JsonConfigurationSource和

  1: public static class IniConfigurationExtensions
  2: {
  3:   public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, string path);
  4:   public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, string path, bool optional);
  5:   public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange);
  6:   public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange);
  7: }