閱讀546 返回首頁    go 阿裏雲 go 技術社區[雲棲]


.NET Core采用的全新配置係統[9]: 為什麼針對XML的支持不夠好?如何改進?

物理文件是我們最常用到的原始配置的載體,最佳的配置文件格式主要由三種,它們分別是JSON、XML和INI,對應的配置源類型分別是JsonConfigurationSource、XmlConfigurationSource和IniConfigurationSource。但是對於.NET Core的配置係統來說,我們習以為常的XML反倒不是理想的配置源,至少和JSON比較起來,它具有一個先天不足的劣勢,那就是針對集合數據結構的支持不如人意。[ 本文已經同步到《ASP.NET Core框架揭秘》之中]

在《配置模型設計詳解》一文中我們對配置模型的設計和實現進行了詳細介紹。在此文中我們說應用中的配置體現為一種樹形化的層次結構,所我將它稱為“配置樹”,具體的配置數據通過配置樹的“葉子節點”承載。當配置數據從不同的來源加載之後都會轉換成一個字典,我將其稱為“配置字典”。為了讓“配置字典”能夠存儲“配置樹”的所有數據和自身結構,我們需要在配置字典中存儲所有葉子節點,葉子節點的路徑和值將直接作為字典元素的Key和Value。由於字典的Key是唯一的,這就要求配置樹中的每一個節點必須具有唯一的路徑。XmlConfigurationSource/XmlConfigurationProvider不能很好地支持集合數據結構的問題就出現在這裏。

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

舉個簡單的例子,假設需要采用XML來表示一個Profile對象的集合(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>

對於這段XML結構,XmlConfigurationProvider會采用“簡單粗暴”的方式將它映射為如下所示的“配置樹”。由於這棵樹直接將XML元素的名稱作為配置節點名稱,所以三個Profile對象在這棵樹中的根節點都以“Profile”命名,毫無疑問,。

image

之所以XML不能像JSON格式那樣可以以一種很自然的形式表示集合或者數組,是因為後者對這兩種數據類型提供了明確的定義方式(采用中括號定義),但是XML隻有子元素的概念,我們不能確定它的子元素是否是一個集合。如果做這樣一個假設:。根據這麼一個假設,我們對XmlConfigurationSource略加改造就可以解決XML難以表示集合數據結構的問題。

我們通過派生XmlConfigurationSource創建一個新的ConfigurationSource類型,姑且將其命名為ExtendedXmlConfigurationSource。XmlConfigurationSource提供的ConfigurationProvdier類型為ExtendedXmlConfigurationProvider,它派生於XmlConfigurationProvider。在重寫的Load方法中,ExtendedXmlConfigurationProvider通過對原始的XML結構進行相應的改動,從而讓原本不合法的XML(XML元素具有相同的名稱)可以轉換成一個針對集合的配置字典 。下圖展示了XML結構轉換采用的規則和步驟。

12

如上圖所示,針對集合對原始XML所作的結構轉換由兩個步驟組成。第一步為表示集合元素的XML元素添加一個名為“”的屬性(Attribute),我們采用零基索引作為該屬性的值。第二步會根據第一步轉換的結果創建一個新的XML,同名的集合元素(比如<profile>)將會根據添加的索引值從新命名(比如<>)。毫無疑問,轉換後的這個XML可以很好地表示一個集合對象。如下所示的是ExtendedXmlConfigurationProvider的定義,上述的這個轉換邏輯體現在重寫的Load方法中。

   1: public class ExtendedXmlConfigurationProvider : XmlConfigurationProvider
   2: {
   3:    public ExtendedXmlConfigurationProvider(XmlConfigurationSource source) : base(source)
   4:     {}
   5:  
   6:     public override void Load(Stream stream)
   7:     {
   8:         //加載源文件並創建一個XmlDocument        
   9:         XmlDocument sourceDoc = new XmlDocument();
  10:         sourceDoc.Load(stream);
  11:  
  12:         //添加索引
  13:         this.AddIndexes(sourceDoc.DocumentElement);
  14:  
  15:         //根據添加的索引創建一個新的XmlDocument
  16:         XmlDocument newDoc = new XmlDocument();
  17:         XmlElement documentElement = newDoc.CreateElement(sourceDoc.DocumentElement.Name);
  18:         newDoc.AppendChild(documentElement);
  19:  
  20:         foreach (XmlElement element in sourceDoc.DocumentElement.ChildNodes)
  21:         {
  22:             this.Rebuild(element, documentElement, 
  23:                 name => newDoc.CreateElement(name));
  24:         }
  25:  
  26:         //根據新的XmlDocument初始化配置字典
  27:         using (Stream newStream = new MemoryStream())
  28:         {
  29:             using (XmlWriter writer = XmlWriter.Create(newStream))
  30:             {
  31:                 newDoc.WriteTo(writer);
  32:             }
  33:             newStream.Position = 0;
  34:             base.Load(newStream);
  35:         }
  36:     }
  37:  
  38:     private void AddIndexes(XmlElement element)
  39:     {
  40:         if (element.ChildNodes.OfType<XmlElement>().Count() > 1)
  41:         {
  42:             if (element.ChildNodes.OfType<XmlElement>().GroupBy(it => it.Name).Count() == 1)
  43:             {
  44:                 int index = 0;
  45:                 foreach (XmlElement subElement in element.ChildNodes)
  46:                 {
  47:                     subElement.SetAttribute("append_index", (index++).ToString());
  48:                     AddIndexes(subElement);
  49:                 }
  50:             }
  51:         }
  52:     }
  53:  
  54:     private void Rebuild(XmlElement source, XmlElement destParent, Func<string, XmlElement> creator)
  55:     {
  56:         string index = source.GetAttribute("append_index");
  57:         string elementName = string.IsNullOrEmpty(index) ? source.Name : $"{source.Name}_index_{index}";
  58:         XmlElement element = creator(elementName);
  59:         destParent.AppendChild(element);
  60:         foreach (XmlAttribute attribute in source.Attributes)
  61:         {
  62:             if (attribute.Name != "append_index")
  63:             {
  64:                 element.SetAttribute(attribute.Name, attribute.Value);
  65:             }
  66:         }
  67:  
  68:         foreach (XmlElement subElement in source.ChildNodes)
  69:         {
  70:             Rebuild(subElement, element, creator);
  71:         }
  72:     }
  73: }

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-25 11:05:56

  上一篇:go  .NET Core???????????????????????????[8]: ???????????????????????????????????????-??????-????????????-?????????
  下一篇:go  機器視覺領域布局前瞻