XML - 使用物件當作內容的介面吧。(序列化與反序列化)

本文將說明如何對XML進行序列化與反序列化。(就是將【物件】變成【XML內容】,【XML內容】變成【物件】)

前言

XML雖然可以直接透過XmlDocument進行內容的存取,但為了往後的維護人員與接手開發的同仁著想,若能物件化最好就物件化以方便控管,報復社會的事情沒必要加在同為苦勞人的工程師上。

 

使用元件

XmlSerializer ( https://msdn.microsoft.com/zh-tw/library/system.xml.serialization.xmlserializer(v=vs.110).aspx )

 

開始實作

準備一個物件用來對Xml進行存取,這個物件的結構將會影響到Xml內容的結構。

public class XmlDTO
{
    public string Element1 { get; set; }
    public string Element2 { get; set; }
}

接著序列化就可以得到結果

//using System.Xml.Serialization;
var serilizer = new XmlSerializer(typeof(XmlDTO));

serilizer.Serialize(Console.Out, new XmlDTO
{
	Element1 = "Value1",
	Element2 = "Value2"
});

 

可以看到物件內屬性(欄位)預設會被當作XmlElement處理(就是XML的NODE節點),如果說想要設定類別(Attribute)的話要怎麼處理?答案就是加上標籤強制設定。修改一下物件的內容就可以了,非常方便。

public class XmlDTO
{
	[XmlAttribute]
	public string Element1 { get; set; }

	[XmlElement]//Default Type
	public string Element2 { get; set; }
}

接著若是一個物件包著一個物件的多層結構,也是如同一般設定物件一樣,直接在放一個物件類別的屬性(欄位)即可。(PS:類別指定的同時也可以一起指定XML對應名稱,用來處理一些特殊符號,像是減號)

public class XmlDTO
{
	[XmlAttribute]
	public string Element1 { get; set; }

	[XmlElement]
	public string Element2 { get; set; }

	public XmlDTO_Child FirstNode { get; set; }
}
public class XmlDTO_Child
{
	[XmlElement("Element1")] //強制指定類別並設定對應XML文檔名稱
	public string NodeElement1 { get; set; }

	[XmlAttribute]//強制指定類別
	public string NodeElement2 { get; set; }

	//default mapping
	public string NodeElement3 { get; set; }

}

針對多筆的資料,可以設定為List或Array(個人偏好List,操作比較方便)

public class XmlDTO
{
	[XmlAttribute]
	public string Element1 { get; set; }

	[XmlElement]
	public string Element2 { get; set; }

	public XmlDTO_Child FirstNode { get; set; }

	public List<XmlDTO_Child> ChildNode { get; set; }
}

另外由於序列化的目標是Stream,因此在輸出檔案的部分語法也可以很簡單

var serilizer = new XmlSerializer(typeof(XmlDTO));
var XmlDTO = new XmlDTO
{
	Element1 = "Value1",
	Element2 = "Value2",
	FirstNode = new XmlDTO_Child { NodeElement1 = "NodeElement1內容", NodeElement2 = "NodeElement2內容", NodeElement3 = "NodeElement3內容" },
	ChildNode = new List<XmlDTO_Child>() { new XmlDTO_Child { NodeElement1 = "NodeElement1Value_1", NodeElement2 = "NodeElement2V_1", NodeElement3 = "NodeElement3V_1" }, new XmlDTO_Child { NodeElement1 = "NodeElement1Value_2", NodeElement2 = "NodeElement2V_2", NodeElement3 = "NodeElement3V_2" } }
};
using (var fileTemp = File.OpenWrite("E://XmlForDemo.xml"))
{
	serilizer.Serialize(fileTemp, XmlDTO);
}

若是要從XML檔案中反序列化成為物件呢?這樣就搞定了,真的還挺不錯對吧?

using (var fileTemp = File.OpenRead("E://XmlForDemo.xml"))
{
	var dtoResult = serilizer.Deserialize(fileTemp) as XmlDTO;
}

PS:若資源是透過API取得的話,也可以直接使用回傳的Stream進行反序列化。

 

完整程式碼

using System;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace Core
{
    public class XmlSerializerProcess
    {
        public void Run()
        {

            var serilizer = new XmlSerializer(typeof(XmlDTO));

            //序列化->輸出Console
            serilizer.Serialize(Console.Out, new XmlDTO
            {
                Attr1 = "Attr1V",
                Attr2 = "Attr2V",
                ListAttr = new List<string> { "A A ", "B B ", " C C" },
                Element = "Attr4V",
                ListElement = new List<string> { "節點1", "節點2", "節點3" },
                FirstNode = new XmlDTO_Child { NodeElement1 = "E1", NodeElement2 = "E2", NodeElement3 = "E3" },
                ChildNode = new List<XmlDTO_Child>() { new XmlDTO_Child { NodeElement1 = "NodeElement1Value", NodeElement2 = "NodeElement2V" } }
            });

            //序列化->檔案儲存
            //using (var fileTemp = File.OpenWrite("E://XmlForDemo.xml"))//路徑請自己修改
            //{
            //    serilizer.Serialize(fileTemp, new XmlDTO
            //    {
            //        Attr1 = "Attr1V",
            //        Attr2 = "Attr2V",
            //        ListAttr = new List<string> { "A A ", "B B ", " C C" },
            //        Element = "Attr4V",
            //        ListElement = new List<string> { "節點1", "節點2", "節點3" },
            //        FirstNode = new XmlDTO_Child { NodeElement1 = "E1", NodeElement2 = "E2", NodeElement3 = "E3" },
            //        ChildNode = new List<XmlDTO_Child>() { new XmlDTO_Child { NodeElement1 = "NodeElement1Value", NodeElement2 = "NodeElement2V" } }
            //    });
            //}

            //反序列化,讀取檔案內容轉變為物件
            //using (var fileTemp = File.OpenRead("E://XmlForDemo.xml"))
            //{
            //    var dtoResult = serilizer.Deserialize(fileTemp) as XmlDTO;
            //}

        }
    }
}
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.IO;

namespace Core
{
    [XmlRoot("ClassRoot")]
    public class XmlDTO
    {
        [XmlAttribute]
        public string Attr1 { get; set; }

        [XmlAttribute]
        public string Attr2 { get; set; }

        [XmlAttribute]
        public List<string> ListAttr { get; set; }

        public XmlDTO_Child FirstNode { get; set; }

        public List<XmlDTO_Child> ChildNode { get; set; }

    }
    public class XmlDTO_Child
    {
        [XmlElement("Element1")] //強制指定類別並設定對應XML文檔名稱
        public string NodeElement1 { get; set; }

        [XmlAttribute]//強制指定類別
        public string NodeElement2 { get; set; }

        //default mapping
        public string NodeElement3 { get; set; }

    }



}

 

注意事項

類別的成員不能有介面(property can not be a interface class),例如你可以使用List當作集合的類別,但是不可以使用IEnumerable

 

 

後語

1.關於XML結構可以參考這篇文章:[入門][XML] XML入門系列 (1) : XML 初論 (https://dotblogs.com.tw/johnny/2010/01/25/13303)

(或上W3School看專門的介紹:https://www.w3schools.com/xml/default.asp)

2.網路上很多關於物件設置的要點經過實測後發現....現在大多都不需要了,實測下來需要處理的就是Element / Atrtribute。

3.之前介紹的使用XML自訂WebConfig其實也可以用這個方式設置.,不過使用這個方式的話,資料內容就相會要求嚴謹,錯一個內容就會整份都錯。(反序列化時)