程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 我的WCF之旅(4):WCF中的序列化(Serialization)- Part I

我的WCF之旅(4):WCF中的序列化(Serialization)- Part I

編輯:關於.NET

SOA 和Message

Windows Communication Foundation (WCF) 是基於面向服務架構(Service Orientation Architecture——SOA)的一種理想的分布式技術(Distributed Technology), 相信在今後在建立基於SOA企業級別的解決方案和進行系統集成方面將會大有作為。一個基於SOA結構的互聯系統(Connected System)通常由若干相互獨立的子系統(Sub-System)組成,這些子系統可能一個獨立的Application,也可能是由若干Application相互集成共同完成一組相關的任務的小系統。這些子系統以一種有效的方式組合、集成為我們聽過一種具有綜合功能的解決方案。

在一個基於SOA的分布式系統中,各個子系統相互獨立又相互關聯。說它們的相互獨立是因為他們各個都是一個個自治的系統(Autonomous System),可以實行各自的版本策略和部署策略,而這種版本的部署上的變動通常不應該引起系統中其他部分的變動。說它們又彼此關聯,則是因為一個子系統所需要的功能往往由其他某個子系統來實現,我們把實現功能的一方稱為Service 的提供者(Provider),把使用Service的一方稱為客戶(Client)。Client依賴於Service的調用,而不失依賴於Service的實現,只要Service的調用方式沒有發生改變,Service的實現變動對於Service的使用者來說是完全透明的。在WCF中,我們把Service的調用相關的提取出來即為我們經常說的Contract,Service的提供者和Client之間共享的是Service Contract——而不傳統OO概念下的Type。把相對穩定的Service Contract和經常變動的Service Implementation相互分布早就了我們互聯系統的松耦合性(Loosely Couple)。

前面我們簡單介紹了SOA系統的基本特征——子系統之間的松耦合性(Loosely Couple);各個子系統的自治性(Autonomous);共享Contract。此外SOA還有其他的一些特征,最重要的一個特征就它是一個基於Message(Message-Based)的系統。子系統之間的相互交互由Message來實現。Client向Service的提供者發送一個Soap Message來訪為他所需要的Service,Service的提供者監聽到來自Client的請求,創建相應的Service對象,執行相關的操作,把執行的結果(Result)以Message 的形式發回給對應的Client。所以我們可以說子系統之間的相互交互本質上是一種消息的交互過程(Message Exchange)。不同的交互方式對應不同的Message Exchange Pattern——MEP。

理解了SO的基本原理,我們來看看WCF,從WCF的全稱來分析——Windows Communication Foundation,顧名思義,他就是解決分布式互聯系統中各相互獨立的子系統如何交互的問題,換句話說,它實際上就是提供 一個基礎構架(Infrastructure)實現Application的通信問題。我們前邊已經提到,各個子系統之間是通過XML Message進行交互的,所以我們可以 把WCF看成是一個完全處理XML Message的構架,WCF的所有的功能都是圍繞著Message來展開的——如何把一個Service的調用轉或稱一個Message Exchange(Service Contract);如何實現一般的.NET對象和能夠容納於XML Message中的XML Infoset之間的轉化(Serialization和Deserialization);如何實現承載數據的XML Infoset和能夠用於網絡傳遞的字節流(Byte Stream)之間的相互轉化(Encoding和Deconding);如何保證置於Message中數據的一致性和防止被惡意用戶竊取以及驗證調用Service和通過Service的合法性(Security:Confidentiality,Integrity,Authentication——CIA);如何保證Message被可靠地被傳達到所需的地方(Reliable Messaging);以及如何把若干次Service調用——本質上是若干次Message Exchange納入到一個單獨的Conversation(Session Support 和Transaction Support……

在分布式系統中,一個Application與另一個Application之間進行交互,必然需要攜帶數據。前面我們說了,系統交互完全是應Message的方式進行的,Message是XML,當然置於Message中的數據也應該是XML(XML Infoset)。如何處理這些交互的數據,我們可能首先想到的就是直接處理XML,我們可以在XML級別通過相關的XML技術——XSD,XPath,XSLT來操作數據。但是要使我們處理後的XML需要和要求的完全一致,這樣的工作無疑是非常枯燥乏味而且費時費力的。而我們最擅長的就是使用.NET對象來封裝我們的數據。如何使我們創造的對象能夠有效地轉化成結構化的XML Infoset,就是今天我們要講的內容——Serialization。

Serialization V.S. Encoding

Serialization可以看成是把包含相同內容的數據從一種結構 (.NET Object) 轉換成另一種結構 (XML) 。要實現在兩種不同結構之間的轉化,這兩種結構之間必須存在一種Mapping。Serialization的是實現由序列化器(Serializer)來負責。而Serializer則利用某種算法(Arithmetic)來提供這種Mapping。我們知道對於一個Managed Type的結構信息——比如它的所有成員的列表,每個成員的Type、訪問限制,以及定在每個成員上的屬性,作為原數據被存貯在Assembly的原數據表中,這些原數據可以通過反射的機制獲得。而XML的結構一般利用XSD來定義。所以 在WCF中的Serialization可以看成是Serializer通過反射的機制分析對象所對應的Type的原數據,從而提供一種算法實現Managed Type的XSD的轉化。

很多剛剛接觸WCF的人往往不能很好地區分Serialization和Encoding。我們的.NET Object通過Serialization轉化成XML Infoset。但是要使我們的數據能夠通過網絡協議在網絡上傳遞,必須把生成的XML Infoset轉化成字節流(Byte Stream)。所以Encoding關注的是XML Infoset到字節流(Byte Stream)這一段轉化的過程。在WCF中,有3中不同的方式可供選擇:Binary;Text和MTOM(Message Transmit Optimized Mechanism)。Binary具有最好的Performance,Text具有最好的互操作性,MTOM則有利於大量數據的傳送。

我們可以這樣來理解Serialization和Encoding,Sterilization是基於Service Contract的——而實際上它也是定義在Service Contract中,是放在我們的Code中;而Encoding一般由Binding提供,它是和Service無關的,我們一般在Configuration中根據實際的需要選擇我們合適的Encoding。WCF把Serialization和Encoding相互分離是有好處的,Serialization手部署環境的影響相對不大,具有相對的通用性,而Encoding則關系到訪問Service的性能以及互操作性等方面,和部署環境緊密相關。比如對於一個在一個Intranet內部使用的系統,往往處於提高Performance考慮,我們一般是使用TCP Transport結合Binary,可能在某一天出現的來自於Internet的潛在的調用,我們不得不改用Http作為Transport,並使用Text Encoding。由於Encoding是可配置的,所以在這種情況下,我們只需要改變Configuration文件就可以了。

DataContractSerializer

Serialization 是通過Serializer來完成的,在WCF中,我們有3種不同的Serializer——DataContractSerializer(定義在System.RunTime.Serializtion namespace中)、XMLSerializer(定義在System.XML.Serialization namespace)和NetDataContractSerializer (定義在System.XML.Serialization namespace) 。他們不同的方式實現.NET Object的Serialization。由於DataContractSerializer和NetDataContractSerializer基本上沒有太大的區別,我們只討論DataContractSerializer和XMLSerializer。其中DataContractSerializer為WCF默認的Serializer,如果沒有顯式定采用另外一種Serializer,WCF會創建一個DataContractSerializer 序列化NET Object。首先我們來討論DataContractSerializer采用怎樣的一種Mapping方式來把.NET Object轉化成XML。我們照例用實驗來說明問題。

我們創建兩個類DataContractProduct和DataContractOrder用於表示產品和訂單兩個實體,讀者完全可以命知道描述的內容,這裡不作特別的介紹。 

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
namespace Artech.WCFSerialization
{
  [DataContract]
  public class DataContractProduct
  {
    Private Fields#region Private Fields
    private Guid _productID;
    private string _productName;
    private string _producingArea;
    private double _unitPrice;
    #endregion
    Constructors#region Constructors
    public DataContractProduct()
    {
      Console.WriteLine("The constructor of DataContractProduct has been invocated!");
    }
    public DataContractProduct(Guid id, string name, string producingArea, double price)
    {
      this._productID = id;
      this._productName = name;
      this._producingArea = producingArea;
      this._unitPrice = price;
    }
    #endregion
    Properties#region Properties
    [DataMember]
    public Guid ProductID
    {
      get { return _productID; }
      set { _productID = value; }
    }
    [DataMember]
    public string ProductName
    {
      get { return _productName; }
      set { _productName = value; }
    }
    [DataMember]
    private string ProducingArea
    {
      get { return _producingArea; }
      set { _producingArea = value; }
    }
    [DataMember]
    public double UnitPrice
    {
      get { return _unitPrice; }
      set { _unitPrice = value; }
    }
    #endregion
  }
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
namespace Artech.WCFSerialization
{
  [DataContract]
  [KnownType(typeof(DataContractOrder))]
  public class DataContractOrder
  {
    private Guid _orderID;
    private DateTime _orderDate;
    private DataContractProduct _product;
    private int _quantity;
    Constructors#region Constructors
    public DataContractOrder()
    {
      this._orderID = new Guid();
      this._orderDate = DateTime.MinValue;
      this._quantity = int.MinValue;
      Console.WriteLine("The constructor of DataContractOrder has been invocated!");
    }
    public DataContractOrder(Guid id, DateTime date, DataContractProduct product, int quantity)
    {
      this._orderID = id;
      this._orderDate = date;
      this._product = product;
      this._quantity = quantity;
    }
    #endregion
    Properties#region Properties
    [DataMember]
    public Guid OrderID
    {
      get { return _orderID; }
      set { _orderID = value; }
    }
    [DataMember]
    public DateTime OrderDate
    {
      get { return _orderDate; }
      set { _orderDate = value; }
    }
    [DataMember]
    public DataContractProduct Product
    {
      get { return _product; }
      set { _product = value; }
    }
    [DataMember]
    public int Quantity
    {
      get { return _quantity; }
      set { _quantity = value; }
    }
    #endregion
    public override string ToString()
    {
      return string.Format("ID: {0}\nDate:{1}\nProduct:\n\tID:{2}\n\tName:{3}\n\tProducing Area:{4}\n\tPrice:{5}\nQuantity:{6}",
        this._orderID, this._orderDate, this._product.ProductID, this._product.ProductName, this._product.ProducingArea, this._product.UnitPrice, this._quantity);
    }
  }
}

使用DataContractSerializer序列化.NET Object。相關的Type必須運用System.Runtime.Serialization. DataContractAttribute, 需要序列化的成員必須運用System.Runtime.Serialization. DataMemberAttribute。為了使我們能夠了解DataContract默認的Mapping機制,我們暫時不在DataContractAttribute和DataMemberAttribute設置任何參數。下面我們 來編寫具體的Serialization的代碼:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using System.Diagnostics;
namespace Artech.WCFSerialization
{
  class Program
  {
    static string _basePath = @"E:\Projects\Artech.WCFSerialization\Artech.WCFSerialization\";
    static void Main(string[] args)
    {
      SerializeViaDataContractSerializer();
    }
    static void SerializeViaDataContractSerializer()
    {
      DataContractProduct product = new DataContractProduct(Guid.NewGuid(), "Dell PC", "Xiamen FuJian", 4500);
      DataContractOrder order = new DataContractOrder(Guid.NewGuid(), DateTime.Today, product, 300);
      string fileName = _basePath + "Order.DataContractSerializer.xml";
      using (FileStream fs = new FileStream(fileName, FileMode.Create))
      {
        DataContractSerializer serializer = new DataContractSerializer(typeof(DataContractOrder));
        using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(fs))
        {
          serializer.WriteObject(writer, order);
        }
      }
      Process.Start(fileName);
    }
  }
}

代碼很簡單,這裡不作特別介紹。我們現在只關心生成XML是怎樣的結構:

這裡我們總結出以下的Mapping關系:

1.Root Element為對象的Type Name——DataContractOrder

2.Type的Namespace會被加到XML根節點的Namespace中http://schemas.datacontract.org/2004/07/Artech.WCFSerialization

3.對象的所有成員以XML Element的形式而不是以XML Attribute的形式輸出。

4.所以對象在XML的輸出順序是按照字母排序。

5.所有成員的Elelement 名稱為成員名稱。

6.不論成員設置怎樣的作用域(public,protected,internal,甚至市Private),

所有運用了DataMemberAttribute的成員均被序列化到XML中——private string ProducingArea。

7.Type和成員必須運用DataContractAttribute和DataMemberAttribute才能被序列化。

上面這些都是默認的Mapping關系,在通常情況下我們用默認的這種Mapping往往不能滿足我們的需求,為了把.NET序列化成我們需要的XML 結構(比如我們的XmL必須於我們預先定義的XSD一致),我們可以在這兩個Attribute(DataContractAttribute和DataMemberAttribute)制定相關的參數來實現。具體做法如下。

1.Root Element可以通過DataContractAttribute中的Name參數定義。

2.Namespace可以通過DataContractAttribute中的NameSpace參數定義。

3.對象的成員只能以XML Element的形式被序列化。

4.對象成員對應的XML Element在XML出現的位置可以通過DataMemberAttribute的Order參數來定義。

5.對象成員對應的Element的名稱可以通過DataMemberAttribute中的Name定義。

6.如果不希望某個成員輸出到XML中,可以去掉成員對應的DataMemberAttribute Attribute。

此外DataMemberAttribute還有連個額外的參數:

1.IsRequired:制定該成員為必須的,如果通過工具生成XSD的話,對應的Element的minOccur=“1”

2.EmitDefaultValue:制定是否輸入沒有賦值的成員(值為默認值)是否出現在XML中。

基於上面這些,現在我們修改我們的DataContractOrder和DataContractProduct。

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
namespace Artech.WCFSerialization
{
  [DataContract(Name = "product", Namespace = "http://Artech.WCFSerialization/Samples/Product")]
   public class DataContractProduct
  {
    Private Fields#region Private Fields
    private Guid _productID;
    private string _productName;
    private string _producingArea;
    private double _unitPrice;
    #endregion
    Constructors#region Constructors
    public DataContractProduct()
    {
      Console.WriteLine("The constructor of DataContractProduct has been invocated!");
    }
    public DataContractProduct(Guid id, string name, string producingArea, double price)
    {
      this._productID = id;
      this._productName = name;
      this._producingArea = producingArea;
      this._unitPrice = price;
    }
    #endregion
    Properties#region Properties
    [DataMember(Name ="id",Order = 1)]
    public Guid ProductID
    {
      get { return _productID; }
      set { _productID = value; }
    }
    [DataMember(Name = "name", Order = 2)]
    public string ProductName
    {
      get { return _productName; }
      set { _productName = value; }
    }
    [DataMember(Name = "producingArea", Order = 3)]
    internal string ProducingArea
    {
      get { return _producingArea; }
      set { _producingArea = value; }
    }
    [DataMember(Name = "price", Order = 4)]
    public double UnitPrice
    {
      get { return _unitPrice; }
      set { _unitPrice = value; }
    }
    #endregion
  }
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
namespace Artech.WCFSerialization
{
  [DataContract(Name ="order",Namespace="http://Artech.WCFSerialization/Samples/Order")]
   public class DataContractOrder
  {
    private Guid _orderID;
    private DateTime _orderDate;
    private DataContractProduct _product;
    private int _quantity;
    Constructors#region Constructors
    public DataContractOrder()
    {
      this._orderID = new Guid();
      this._orderDate = DateTime.MinValue;
      this._quantity = int.MinValue;
      Console.WriteLine("The constructor of DataContractOrder has been invocated!");
    }  [KnownType(typeof(DataContractOrder))]
    public DataContractOrder(Guid id, DateTime date, DataContractProduct product, int quantity)
    {
      this._orderID = id;
      this._orderDate = date;
      this._product = product;
      this._quantity = quantity;
    }
    #endregion
    Properties#region Properties
    [DataMember(Name ="id",Order =1)]
    public Guid OrderID
    {
      get { return _orderID; }
      set { _orderID = value; }
    }
    [DataMember(Name = "date", Order = 2)]
    public DateTime OrderDate
    {
      get { return _orderDate; }
      set { _orderDate = value; }
    }
    [DataMember(Name = "product", Order = 3)]
    public DataContractProduct Product
    {
      get { return _product; }
      set { _product = value; }
    }
    [DataMember(Name = "quantity", Order = 4)]
    public int Quantity
    {
      get { return _quantity; }
      set { _quantity = value; }
    }
    #endregion
    public override string ToString()
    {
      return string.Format("ID: {0}\nDate:{1}\nProduct:\n\tID:{2}\n\tName:{3}\n\tProducing Area:{4}\n\tPrice:{5}\nQuantity:{6}",
        this._orderID, this._orderDate, this._product.ProductID, this._product.ProductName, this._product.ProducingArea, this._product.UnitPrice, this._quantity);
    }
  }
}

再次進行序列化,看看得到的XML是什麼樣子?

<order xmlns="http://Artech.WCFSerialization/Samples/Order" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <id>994b42c4-7767-4ed4-bdf8-033e99c00a64</id>
 <date>2007-03-09T00:00:00+08:00</date>
 <product xmlns:a="http://Artech.WCFSerialization/Samples/Product">
   <a:id>137e6c34-3758-413e-8f8a-83f26f78a174</a:id>
  <a:name>Dell PC</a:name>
  <a:producingArea>Xiamen FuJian</a:producingArea>
  <a:price>4500</a:price>
 </product>
 <quantity>300</quantity>
</order>
注:對於DataContract Serializer,這裡有兩點需要我們注意的:

1.由於Serialization是對數據的不同結構或形態的轉化,在轉化過程中必須預先知道兩種數據相關的原數據(Metadata)。而對於每個.NET對象來說,它的數據結果存放在他所對應的Assembly的原數據表中(Metadata Table),這些原數據表定義的每個定義在該Assembly中的Type的成員定義——包括成員的Type,訪問作用域,成員名稱,以及運用在成員上的所有Attribute。原則上,只要知道.NET對象對應的Type,我們就可以通過反射(Reflection)的機制分析出該對象的結構。在該例子中,Serializer要序列化DataContractOrder的對象,必須首先知道該對象所屬的Type——這個Type通過構造函數傳遞給Serializer。但是DataContractOrder定義了一個特殊的成員Product,他屬於我們的自定義Type:DataContractProduct(這裡需要特別指出的是對於.NET的基元類型——Primary Type和一半的常用Type——比如DateTime,Guid等等,Serializer可以自動識別,所以不用特別指定),Serializer是不會識別這個對象的,所以我們需要在定義DataContractOrder的時候運用KnownType Attribute來這個Type。

2.因為在傳統的分布式應用中,我們廣泛地采用Serializable Attribute來表明該對象是可以序列化的,DataContract Serializer對這種機制也是支持的。

上面我們講了Serialization,它的本質就是把.NETObject轉化具有一定結構的XML Infoset。被序列化的成的XML Infoset記過Encoding被進一步轉化成適合在網絡上傳遞的字節流。當這些字節流從一個Application傳遞到另一個Application,由於我們的程序的業務邏輯處理的是一個個的.NET對象,所以在目標Application, 會以一個相反的過程把接收到的字節流重構成為和原來一樣的.NET Object——目標Application對接收到的字節流記過Decoding轉化成XML Infoset,然後通過創建和用於序列化過程一致的Serializer通過Deserialization重建一個.NET Object。所以這就對Serializer提出了要求——它必須為Managed Type的結構和XML的結構提供可逆性的保證——我們把一個.NET Object序列化成一組XML,然後對這組XML進行反序列化重建的對象必須和原來一致。

現在我們在驗證這種一致性。在上面的Sample中,我們創建了一個DataContractOrder對象,對它進行序列化並把生成的XML保存的一個文件裡面(Order.DataContractSerializer.xml),現在我們都讀取這個文件的內容,把它反序列化成DataContractOrder 對象,看它的內容是否和原來一樣。下面是Deserialization的邏輯。

static void DeserializeViaDataContractSerializer()
    {
      string fileName = _basePath + "Order.DataContractSerializer.xml";
      DataContractOrder order;
      using (FileStream fs = new FileStream(fileName, FileMode.Open))
      {
        DataContractSerializer serializer = new DataContractSerializer(typeof(DataContractOrder));
        using (XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas()))
        {
          order = serializer.ReadObject(reader) as DataContractOrder;
        }
      }
      Console.WriteLine(order);
      Console.Read();
    }

調用這個方法,通過在控制台輸出DataContractOrder的內容,我們可以確定,通過Deserialization生成的DataContractOrder 對象和原來的對象具有一樣的內容。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved