当前位置: www.89677.com > 互联网 > 正文

读取配置文件,深入了解三种针对文件

时间:2019-11-01 20:23来源:互联网
原标题:[.NET Core的Options模式] 针对Options对象的依赖注入[上篇]   物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON、XML和INI,对应的配置源

原标题:[.NET Core的Options模式] 针对Options对象的依赖注入[上篇]

 

物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON、XML和INI,对应的配置源类型分别是JsonConfigurationSource、XmlConfigurationSource和IniConfigurationSource。 [ 本文已经同步到《ASP.NET Core框架揭秘》之中]

做个笔记,原文链接

我们可以利用配置绑定功能将承载一组相关配置数据的IConfiguration对象转换成一个具有兼容数据结构的POCO(Plain Old CLR Object)对象,进而实现“面向对象”的配置编程,而系列介绍的“Options模式(Pattern)”则在此基础上更进了一步,它使我们可以采用依赖注入的方式直接使用绑定的这个POCO对象,我们称这个POCO对象为Options对象。除此之外,Options模式也可以直接脱离配置系统,使我们可以直接采用编程的方式来初始化Options对象。

目录索引 

 

【无私分享:ASP.NET CORE 项目实战】目录索引

 

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

除了应用 IOptions<T> .Value的方式对配置信息进行全局注册外可以应用的另一个微软给出的组件,需要依赖两个包

01

简介

 

  我们在 读取配置文件(一) appsettings.json 中介绍了,如何读取appsettings.json.

  但随之产生了问题:我们使用的是在 Startup.cs 中(如下图)来实现配置读取,有两个问题 ① 我们如果定义N种配置,是否要再这里添加N条这样的配置 ; ② 如果我们的配置不想写在appsettings.json中呢 

  澳门威斯尼人6613·com 1

  

 

 

 

一、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、XmlConfigurationSource和IniConfigurationSource这些针对通过文件类型的ConfigurationSource会提供不同类型的ConfigurationProvider来读取对应的配置文件并将读取的内容转换成一个配置字典,但是这些ConfigurationProvider都派生与如下一个FileConfigurationProvider类型。FileConfigurationSource和FileConfigurationProvider都定义在“Microsoft.Extensions.Configuration
.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异常的。

Microsoft.Extensions.Configuration.Binder

以DI的方式使用Options

解决问题

 

  带着上面的两个问题,我们首先来添加一个配置文件 siteconfig.json

  

  {

    "SiteBaseConfig": {

      //文件上传路径

      "FileUpPath": "/upload/",

      //是否启用单用户登录

      "IsSingleLogin": "True",

      //允许上传的文件格式

      "AttachExtension": "gif,jpg,jpeg,png,bmp,rar,zip,doc,docx,xls,xlsx,ppt,pptx,txt,flv,apk,mp4,mpg,ts,mpeg,mp3,bak,pdf",

      //图片上传最大值KB

      "AttachImagesize": 12400

    }
  }

 

我们在 读取文件配置(一)中的配置类 ApplicationConfiguration

 

 1 public class ApplicationConfiguration
 2     {
 3         #region 属性成员
 4 
 5         /// <summary>
 6         /// 文件上传路径
 7         /// </summary>
 8         public string FileUpPath { get; set; }
 9         /// <summary>
10         /// 是否启用单用户登录
11         /// </summary>
12         public bool IsSingleLogin { get; set; }
13         /// <summary>
14         /// 允许上传的文件格式
15         /// </summary>
16         public string AttachExtension { get; set; }
17         /// <summary>
18         /// 图片上传最大值KB
19         /// </summary>
20         public int AttachImagesize { get; set; }
21         #endregion
22     }

 

 

  在 project.json 的 buildOptions 添加 "copyToOutput": "siteconfig.json",让该文件在编译的时候自动拷贝到输出目录下(默认为bin目录):

 

  澳门威斯尼人6613·com 2

 

修改  读取文件配置(一) 的领域层 AppConfigurtaionServices

 

  public class AppConfigurtaionServices
  {

    

    public T GetAppSettings<T>(string key)where T:class,new()

    {

      IConfiguration config = new ConfigurationBuilder()

                  .Add(new JsonConfigurationSource { Path= "siteconfig.json", ReloadOnChange=true })

                  .Build();

 

      var appconfig= new ServiceCollection()

              .AddOptions()

              .Configure<T>(config.GetSection(key))

              .BuildServiceProvider()

              .GetService<IOptions<T>>()

              .Value;

 

      return appconfig;

    }

 

  }  

  

  说明:我们首先创建了一个ConfigurationBuilder对象,并在它上面注册了一个JsonConfigurationSource。在创建这个JsonConfigurationSource对象的时候,除了指定配置文件(“siteconfig.json”)的路径之外,我们还将它的 ReloadOnChange 属性设置为True。这个ReloadOnChange属性的含义就是当原始配置文件的内容发生改变的时候是否需要重新加载配置。

 

  这时候我们会发现 Configure<T>(config.GetSection(key)) 报错:澳门威斯尼人6613·com 3

 

 

  我查看了我们之前在Startup中的 services.Configure<T>() 发现,这个里面的参数是有两个重载的 一个是 IConfiguration 一个是 System.Action<T>,那么为什么这里重载不出来了呢?

 

   十分不解,最后发现是少了扩展类 Microsoft.Extensions.Options.ConfigurationExtensions

 

  我们通过 NuGet 添加这个 Install-Package Microsoft.Extensions.Options.ConfigurationExtensions  问题得到解决,在此感谢 @Artech

 

  我们来测试一下:

  澳门威斯尼人6613·com 4

  澳门威斯尼人6613·com 5

 

 

  OK,这样,我们在文章开始提出的两个问题就都得到了解决!

 

 

 

 

 

希望跟大家一起学习Asp.net Core 

刚开始接触,水平有限,很多东西都是自己的理解和翻阅网上大神的资料,如果有不对的地方和不理解的地方,希望大家指正!

虽然Asp.net Core 现在很火热,但是网上的很多资料都是前篇一律的复制,所以有很多问题我也暂时没有解决,希望大家能共同帮助一下!

 

原创文章 转载请尊重劳动成果 http://yuangang.cnblogs.com

 

二、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: }

Microsoft.Extensions.Options.ConfigurationExtensions

所谓的Options模式,其本质就是利用预先注册的一些服务来提供承载配置数据的Options对象,这些原始的数据可能来源于配置系统提供的IConfiguration对象,也可能是我们在应用启动过程中自动创建并初始化的。我们先来演示采用配置系统作为Options对象数据源的场景。简单起见,我们依然沿用《配置》系列定义的Profile作为基础的Options类型,如下是相关类型的定义。

三、XmlConfiguationSource & XmlConfiguationProvider

奥门威尼斯人游戏,XML也是一种常用的配置定义形式,它对数据的表达能力甚至强于JSON,基于所有类型的数据结构都可以通过XML表示出来。当我们通过一个XML元素表示一个复杂对象的时候,对象的数据成员定义成当前XML元素的子元素。如果数据成员是一个简单数据类型,我们还可以选择将其定义成当前XML元素的属性(Attribute)。针对一个Profile对象,我们可以采用如下两种不同的形式来定义。

   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>

虽然XML对数据结构的表达能力总体要强于JSON,但是作为配置模型的数据来源却有自己的局限性,比如它们对集合的表现形式有点不尽如人意。举个简单的例子,对于一个元素类型为Profile的集合,我们可以采用具有如下结构的XML来表现。

   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>

但是这段XML却不能正确地转换成配置字典,原因很简单,因为字典的Key必须是唯一的,这必然要求最终构成配置树的每个节点必须具有不同的路径。上面这段XML很明显不满足这个基本的要求,因为表示一个Profile对象的三个XML元素(<Profile>...</Profile>)是“同质”的,对于由它们表示的三个Profile对象来说,分别表示性别、年龄、电子邮箱地址和电话号码的四个叶子节点的路径是完全一样的,所以根据无法作为配置字典的Key。通过前面针对配置绑定的介绍我们知道,如果需要通过配置字典来表示一个Profile对象的集合,我们需要按照如下的方式为每个集合元素加上相应的索引(“foo”、“bar”和“baz”)。

   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

按照这样的结构,如果我们需要以XML的方式来表示一个Profile对象的集合,就不得不采用如下的结构,即采用索引来命名集合元素对应的XML元素。当时这样的定义方式从语义的角度来讲是不合理的,因为同一个集合的所有元素应该是“同质”的,同质的XML元素采用不同的名称有点说不过去。根据配置绑定的规则,这样的结构同样可以表示一个由三个元素组成的Dictionary<string, Profile>对象,Key分别是“Foo”、“Bar”和“Baz”。如果用这样的XML来表示一个字典对象,语义上就完全没有问题了。

   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>

针对XML文件的配置源类型为XmlConfigurationSource,该类型定义在“Microsoft.Extensions.Configuration.Xml”这个NuGet包中。如下面的代码片段所示,XmlConfigurationSource通过重写的Build方法创建了一个XmlConfigurationProvider对象。作为抽象类型FileConfigurationProvider的继承者,XmlConfigurationProvider通过重写的Load方法完成了针对XML文件的读取和配置字典的初始化。

   1: public class XmlConfigurationSource : FileConfigurationSource

   2: {

   3:     public override IConfigurationProvider Build(IConfigurationBuilder builder)

   4:     {

   5:         base.FileProvider = base.FileProvider ?? builder.GetFileProvider();

   6:         return new XmlConfigurationProvider(this);

   7:     }

   8: }

   9:  

  10: public class XmlConfigurationProvider : FileConfigurationProvider

  11: {   

  12:     public XmlConfigurationProvider(XmlConfigurationSource source);   

  13:     public override void Load(Stream stream);

  14: }

JsonConfigurationSource的注册可以通过调用针对IConfigurationBuilder的扩展方法AddJsonFile来完成。与之类似,“Microsoft.Extensions.Configuration.Xml”这个NuGet包中同样提供了如下一系列名为AddXmlFile的扩展方法重载来根据指定的XML文件创建相应的XmlConfigurationSource并注册到指定的ConfigurationBuilder对象上。AddXmlFile和AddJsonFile方法具有完全一样的声明。

   1: public static class XmlConfigurationExtensions

   2: {

   3:     public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, string path);

   4:     public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, string path, bool optional);

   5:     public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange);

   6:     public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange);

   7: }

然后一个扩展方法:

publicclassProfile:IEquatable<Profile>

四、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和XmlConfigurationSource的注册可以通过调用IConfigurationBuilder接口的扩展方法AddJsonFile和AddXmlFile来完成,“Microsoft.Extensions.Configuration.Ini”这个NuGet包自然会也会为IniConfigurationSource定义如下所示的AddIniFile扩展方法。

   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: }
public static class ServiceCollectionExtensions
{
    public static TConfig ConfigurePOCO<TConfig>(this IServiceCollection services, IConfiguration configuration, Func<TConfig> pocoProvider) where TConfig : class
    {
        if (services == null) throw new ArgumentNullException(nameof(services));
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));
        if (pocoProvider == null) throw new ArgumentNullException(nameof(pocoProvider));

        var config = pocoProvider();
        configuration.Bind(config);
        services.AddSingleton(config);
        return config;
    }

    public static TConfig ConfigurePOCO<TConfig>(this IServiceCollection services, IConfiguration configuration, TConfig config) where TConfig : class
    {
        if (services == null) throw new ArgumentNullException(nameof(services));
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));
        if (config == null) throw new ArgumentNullException(nameof(config));

        configuration.Bind(config);
        services.AddSingleton(config);
        return config;
    }
}

{

注册的时候:

publicGender Gender { get; set; }

  var mySettings = new MySettings("foo"); 
  services.ConfigurePOCO(Configuration.GetSection("MySettings"), mySettings);
  //或者
  services.ConfigurePOCO(Configuration.GetSection("MySettings"), () => new MySettings("foo"));

publicintAge { get; set; }

就不需要像 IOptions<T>应用之前还得注册services.AddOptions()了。(多配置行代码在团队中对不熟悉又喜欢偷懒的人来说是很烦恼的。。)

publicContactInfo ContactInfo { get; set; }

而且程序在启动时将配置单例注入到内存要好过IOptions的“懒加载”模式,而且避免对象初始化错误造成的运行时错误。

publicProfile(){ }

* *

publicProfile(Gender gender, intage, stringemailAddress, stringphoneNo)

{

Gender = gender;

Age = age;

ContactInfo = newContactInfo

{

EmailAddress = emailAddress,

PhoneNo = phoneNo

};

}

publicboolEquals(Profile other)

{

returnother == null

? false

: Gender == other.Gender &&

澳门威斯尼人6613·com,Age == other.Age &&

ContactInfo.Equals(other.ContactInfo);

}

}

publicclassContactInfo:IEquatable<ContactInfo>

{

publicstringEmailAddress { get; set; }

publicstringPhoneNo { get; set; }

publicboolEquals(ContactInfo other)

{

returnother == null

? false

: EmailAddress == other.EmailAddress &&

PhoneNo == other.PhoneNo;

}

}

publicenumGender

{

Male,

Female

}

在一个简单的控制台应用中定义了上面这些类型之后,我们来创建承载一个Profile对象的配置文件profile.json。如下所示的就是这个JSON文件的内容,它提供了一个构成一个完整Profile对象的所有数据。

{

"gender": "Male",

"age": "18",

"contactInfo": {

"emailAddress": "foobar@outlook.com",

"phoneNo": "123456789"

}

}

接下来我们需要为项目添加相关的NuGet包。由于我们采用基于JSON文件的配置,所以需要添加对应的NuGet包“Microsoft.Extensions.Configuration.Json”。由于Options模式本质上就是借助于依赖注入框架实现的一种编程模式,所以我们需要 “Microsoft.Extensions.DependencyInjection”这个NuGet包。

Options模式的核心接口IOptions<TOptions>和其他核心类型定义在“Microsoft.Extensions.Options”这个NuGet包中,而NuGet“Microsoft.Extensions.Options.ConfigurationExtensions”实现了针对配置模型的集成,但是由于后者依赖前者,所以我们只需要添加针对后者的依赖就可以了。

综上所述,我们需要为当前项目添加针对如下三个NuGet包的依赖:

  • Microsoft.Extensions.Configuration.Json
  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Options.ConfigurationExtensions

接下来我们编写了如下的代码来演示如何采用Options模式来获取由配置文件提供的数据绑定生成的Options对象。我们调用扩展方法AddJsonFile将针对配置文件(“profile.json”)的配置源注册到创建的ConfigurationBuilder对象上,然后利用后者创建出对应的IConfigurataion对象。

接下来我们创建一个ServiceCollection对象,在调用扩展方法AddOptions注册Options编程模式的核心服务后,我们将创建的IConfiguration对象作为参数调用另一个扩展方法Configure<Profile>。Configure<Profile>方法相当于将提供的IConfiguration与Options类型(Profile)做了一个映射,在需要提供对应Options对象的时候, 映射的IConfiguration对象会被提取出来并绑定为提供的Options对象。

classProgram

{

staticvoidMain()

{

varconfiguration = newConfigurationBuilder()

.SetBasePath(Directory.GetCurrentDirectory())

.AddJsonFile( "profile.json")

.Build();

varprofile = newServiceCollection()

.AddOptions()

.Configure<Profile>(configuration)

.BuildServiceProvider()

.GetRequiredService<IOptions<Profile>>()

.Value;

Console.WriteLine( $"Gender: {profile.Gender}");

Console.WriteLine( $"Age: {profile.Age}");

Console.WriteLine( $"Email Address: {profile.ContactInfo.EmailAddress}");

Console.WriteLine( $"Phone No: {profile.ContactInfo.PhoneNo}");

}

}

承载配置数据的Options对象最终是通过IOptions<TOptions>接口表示的服务来提供的,这个接口的泛型参数TOptions表示的正是Options对象的类型。在得到作为DI容器的IServiceProvider对象之后,我们直接调用其扩展方法GetRequiredService的来提供这个IOptions<Profile>对象,该对象Value属性返回的就是通过绑定配置数据生成的Profile对象。

我们将这个Profile对象承载的相关数据直接打印在控制台上。这个程序执行之后,会在控制台上生成如图1所示的输出结果,可见我们通过Options模式得到Profile对象承载的数据完全来源于配置文件。

澳门威斯尼人6613·com 6

图1 绑定配置生成的Profile对象

02

提供具名的Options

针对同一个Options类型,通过IOptions<TOptions>接口表示的服务只能提供一个单一的Options对象,但是在很多情况下我们需要多个具有相同类型的Options对象来承载不同的配置。

就我们演示实例中用来表示个人信息的Profile类型来说,应用程序中可能会使用它来表示不同用户的信息,比如张三、李四和王五。为了解决这个问题,我们可以在添加IConfiguration对象与Options类型映射关系的时候赋予它们一个唯一标识,这个标识最终被用于针对性的提取对应的Options对象。这种具名的Options对象由另一个名为IOptionsSnapshot<TOptions>的接口表示的服务来提供。

同样是针对我们上面演示的这个实例,假设现在我们的应用需要采用Options模式提取针对不同用户的信息,具有又该如何实现呢?由于我们采用JSON格式的配置文件来提供原始的用户信息,所以我们需要将针对多个用户的信息定义在profile.json文件中。我们通过如下的形式提供了两个用户(“foo”和“bar”)的基本信息。

{

"foo": {

"gender": "Male",

"age": "18",

"contactInfo": {

"emailAddress": "foo@outlook.com",

"phoneNo": "123"

}

},

"bar": {

"gender": "Female",

"age": "25",

"contactInfo": {

"emailAddress": "bar@outlook.com",

"phoneNo": "456"

}

}

}

具名Options的注册和提取体现在如下所示的代码片段中。我们从这段代码片段中可以看出,在调用IServiceCollection接口的扩展方法Configure<Profile>的时候,我们给注册的映射关系起了一个名字(“foo”和“bar”),指定的IConfiguration对象也由原来的ConfigurationRoot对象变成它的两个子配置节。

classProgram

{

staticvoidMain()

{

varconfiguration = newConfigurationBuilder()

.SetBasePath(Directory.GetCurrentDirectory())

.AddJsonFile( "profile.json")

.Build();

varserviceProvider = newServiceCollection()

.AddOptions()

.Configure<Profile>( "foo", configuration.GetSection( "foo"))

.Configure<Profile>( "bar", configuration.GetSection( "bar"))

.BuildServiceProvider();

Action<Profile> print = profile =>

{

Console.WriteLine( $"Gender: {profile.Gender}");

Console.WriteLine( $"Age: {profile.Age}");

Console.WriteLine( $"Email Address: {profile.ContactInfo.EmailAddress}");

Console.WriteLine( $"Phone No: {profile.ContactInfo.PhoneNo}n");

};

varoptionsAccessor = serviceProvider.GetRequiredService<IOptionsSnapshot<Profile>>();

print(optionsAccessor.Get( "foo"));

print(optionsAccessor.Get( "bar"));

}

}

为了针对指定的用户名来提取对应的Profile对象,我们利用作为DI容器的ServiceProvider得到通过IOptionsSnapshot<TOptions>接口表示的服务对象,并将用户名作为参数调用其Get方法得到对应的Profile对象。程序运行后,针对两个不同用户的基本信息将以如图2所示的形式输出到控制台上。

图2 根据用户名称提取对应的Profile返回搜狐,查看更多

责任编辑:

编辑:互联网 本文来源:读取配置文件,深入了解三种针对文件

关键词: