本節繼續學習WCF分布式開發步步為贏(7):WCF數據契約與序列化.數據契約是WCF應用程序開發中一個重要的概念,毫無疑問實現客戶端與服務端數據契約的傳遞中序列化是非常重要的步驟。那麼序列化是什麼?為什麼會有序列化機制?或者說它是為了解決什麼問題?作用是什麼?現有的.NET 序列化機制和WCF序列化機制有什麼不同?我們在本節文章裡都會詳細介紹。本節結構:【0】數據契約【1】序列化基本概念【2】.NET 序列化機制【3】WCF序列化機制【4】代碼實現與分析【5】總結。
下面我們正式進入今天的學習階段,首先來介紹一下數據契約的概念:
【0】數據契約(DataContract):
在WCF服務編程中我們知道,服務契約定義了遠程訪問對象和可供調用的服務操作方法,數據契約則是定義服務端和客戶端之間要傳送的自定義數據類型。在WCF項目中,聲明一個類型為DataContract,那麼該類型就可以被序列化在服務端和客戶端之間傳送。類只有聲明為DataContract,該類型的對象才可以被傳送,且只有類的屬性會被傳送,需要在屬性生命前加DataMember聲明,這樣該屬性就可以被序列化傳送。默認情況屬性是不可傳遞的。類的方法不會被傳送。WCF對定義的數據契約的類型可以進行更加細節的控制,可以把一個成員屬性排除在序列化范圍以外,客戶端程序不會獲得被排除在外的成員屬性的任何信息,包括定義和數據。代碼如下:
[DataContract]//數據契約屬性聲明
class MyDataContract
{
[DataMember(Name = "MyName")]//數據成員標記,支持別名定義
public string Name
{
get;
set;
}
[DataMember(Name = "MyEmail")]//數據成員標記,支持別名定義
public string Email
{
get;
set;
}
[DataMember]//數據成員標記
public string Mobile
{
get;
set;
}
//沒有[DataMember]聲明,不會被序列化
public string Address
{
get;
set;
}
}
}
上面類聲明為DataContract,部分屬性聲明為DataMember(數據成員)。可以序列化為客戶端傳送。Address成員屬性沒有被聲明為DataMember,因此在交換數據時,不會傳輸Address的任何信息。聲明為DataMember的成員也可以自定義客戶端可見的別名 如:[DataMember(Name = "MyName")]//數據成員標記,支持別名定義。
【1】序列化基本概念:
知道數據契約的一些概念和特性之後,下面來介紹一下序列化的概念。
【1.1】為什麼序列化:我們這裡先來介紹一下為什麼需要序列化。當然這個不是必須的。只是針對特定的開發平台的數據或者信息類型而言,當一個系統或者說平台需要和別的異構的系統或者平台交互的時候,兩個系統需要一個特定的公開的可以公用的行業標准來支持這個數據信息的交互。這裡目前來說支持這個數據交互傳遞的語言載體就是XML.
同樣WCF作為面向服務的編程框架,它的目標或者特性之一就是實現服務的跨語言、平台,與不同的服務進行信息數據的交互,而不限制客戶端的系統或者開發語言。要實現這個目標,WCF服務首先就是要面對信息的傳遞與共享問題。我們知道WCF服務和客戶端可以傳遞如Int、String等.NET數據類型。但是如何實現用戶自定義復雜類型的跨服務邊界的傳遞,這是一個關鍵問題。數據契約可以發布為服務的元數據,允許客戶端轉化為本地語言表示。解決的辦法就是封送(Marshaling),將對象封送到其它平台。基於WCF的客戶端和服務端參數傳遞的過程如下圖:
主要步驟:客戶端序列化參數為XML信息集--傳遞->服務端反序列化為本地類型--執行結果->序列化結果為XML信息集--傳遞->客戶端序反序列化返回信息為本地類型。
在WCF分布式開發必備知識(2):.Net Remoting一節中也介紹了.Net Remoting的通信過程 ,兩者也有流程也有部分相似之處。對象封送的概念其實.Net Remoting早有涉及,遠程對象(RemoteOject),也就是我們遠程要訪問的對象.首先定義一個Class,繼承MarshalByRefObject,可以使用在remoting應用中,支持對象的跨域邊界訪問。看過.Net Remoting這節文章應該還有點印象,不同之處是WCF的對象封送是為跨越服務邊界,.Net Remoting的封送是為了跨越跨域邊界。相關的概念請查閱WCF分布式開發必備知識(2):.Net Remoting或者MSDN,都能找到詳細的介紹,這裡不在詳述。
【1.2】什麼是序列化:
序列化是指將對象實例的狀態存儲到存儲媒體的過程。在此過程中,先將對象的公共字段和私有字段以及類的名稱(包括類所在的程序集)轉換為字節流,然後再把字節流寫入數據流。在隨後對對象進行反序列化時,將創建出與原對象完全相同的副本。
用戶自定義類型要想在WCF服務端和客戶端傳遞就必須聲明為DataContract。這樣就能實現用戶自定義類型的序列化。序列化的目的就是把一種私有的或者某種平台下使用的數據類型轉化為標准的可以公開交互的數據信息樣式。這個過程就叫序列化。這個也是序列化的作用或者目的之所在。序列化是將對象狀態轉換為可保持或傳輸的格式的過程。與序列化相對的是反序列化,它將流轉換為對象。這兩個過程結合起來,可以輕松地存儲和傳輸數據。序列化的目的:
1、以某種存儲形式使自定義對象持久化;
2、將對象從一個地方傳遞到另一個地方。序列化就是把本地消息或者數據的類型進行封送,轉換為標准的可以跨平台、語言的信息集,為別的系統或者服務所用。
【2】.NET 序列化機制:
.net的序列化。.net是通過反射機制自動實現對象的序列化與反序列化。首先.net能夠捕獲對象每個字段的值,然後進行序列化,反序列化時,.net創建一個對應類型的新的對象,讀取持久化的值,然後設置字段的值。.net對象狀態的序列化到Stream中。.NET Framework 提供三種序列化技術:
1.BinaryFormatter二進制序列化保持類型保真度,這對於在應用程序的不同調用之間保留對象的狀態很有用。例如,通過將對象序列化到剪貼板,可在不同的應用程序之間共享對象。您可以將對象序列化到流、磁盤、內存和網絡等等。遠程處理使用序列化“通過值”在計算機或應用程序域之間傳遞對象。
2.調用System.Runtime.Serialization.Formatters.Soap空間下的SoapFormatter類進行序列化和反序列化,序列化之後的文件是Soap格式的文件(簡單對象訪問協議(Simple
3.XML 序列化需要引用System.Xml.Serialization,使用XmlSerialize類進行序列化和反序列化操作。它僅序列化公共屬性和字段,且不保持類型保真度。基於XML 標准,支持跨平台數據交互。
BinaryFormatter二進制序列化的優點:1. 所有的類成員(包括只讀的)都可以被序列化;2. 性能高。
SoapFormatter、XmlSerialize的優點:1. 跨平台,互操作性好;2. 不依賴二進制;3. 可讀性強。
當然.NET原有的序列化器也有自己的局限性,因為他們除了要序列化對象的狀態信息外,還要將程序集的信息和版本信息持久化到流中,這樣才能保證對象被反序列化為正確的對象類型副本。這要求客戶端必須擁有使用.NET程序集。不能滿足跨平台的WCF數據交互的需求。
【3】WCF序列化機制:
由於.NET格式化器固有的缺陷,WCF不得不提供自己的格式化,以滿足其面向服務的需求。在WCF程序集裡,提供了專門用戶序列化和反序列化操作的類:DataContractSerializer,在System.Runtime.Serialization命名空間裡。使用Reflector查看其定義信息如下:
public abstract class XmlObjectSerializer
{
// Methods
protected XmlObjectSerializer()
{
}
internal static void CheckNull(object obj, string name)
{
if (obj == null)
{
throw new ArgumentNullException(name);
}
}
internal virtual Type GetSerializeType(object graph)
{
if (graph != null)
{
return graph.GetType();
}
return null;
}
private static string GetTypeInfoError(int errorMessage, Type type, Exception innerException)
{
string str = (type == null) ? string.Empty : SR.GetString(1, new object[] { DataContract.GetClrTypeFullName(type) });
string str2 = (innerException == null) ? string.Empty : innerException.Message;
return SR.GetString(errorMessage, new object[] { str, str2 });
}
public abstract bool IsStartObject(XmlDictionaryReader reader);
public virtual bool IsStartObject(XmlReader reader)
{
CheckNull(reader, "reader");
return this.IsStartObject(XmlDictionaryReader.CreateDictionaryReader(reader));
}
public virtual object ReadObject(XmlDictionaryReader reader)
{
return this.ReadObject(reader, true);
}
public virtual object ReadObject(XmlReader reader)
{
CheckNull(reader, "reader");
return this.ReadObject(XmlDictionaryReader.CreateDictionaryReader(reader));
}
public abstract object ReadObject(XmlDictionaryReader reader, bool verifyObjectName);
public virtual object ReadObject(XmlReader reader, bool verifyObjectName)
{
CheckNull(reader, "reader");
return this.ReadObject(XmlDictionaryReader.CreateDictionaryReader(reader), verifyObjectName);
}
public abstract void WriteEndObject(XmlDictionaryWriter writer);
public virtual void WriteEndObject(XmlWriter writer)
{
CheckNull(writer, "writer");
this.WriteEndObject(XmlDictionaryWriter.CreateDictionaryWriter(writer));
}
public virtual void WriteObject(XmlDictionaryWriter writer, object graph)
{
CheckNull(writer, "writer");
try
{
this.WriteStartObject(writer, graph);
this.WriteObjectContent(writer, graph);
this.WriteEndObject(writer);
}
catch (XmlException exception)
{
throw new SerializationException(GetTypeInfoError(0, this.GetSerializeType(graph), exception), exception);
}
catch (FormatException exception2)
{
throw new SerializationException(GetTypeInfoError(0, this.GetSerializeType(graph), exception2), exception2);
}
}
public virtual void WriteObject(XmlWriter writer, object graph)
{
CheckNull(writer, "writer");
this.WriteObject(XmlDictionaryWriter.CreateDictionaryWriter(writer), graph);
}
public abstract void WriteObjectContent(XmlDictionaryWriter writer, object graph);
public virtual void WriteObjectContent(XmlWriter writer, object graph)
{
CheckNull(writer, "writer");
this.WriteObjectContent(XmlDictionaryWriter.CreateDictionaryWriter(writer), graph);
}
public abstract void WriteStartObject(XmlDictionaryWriter writer, object graph);
public virtual void WriteStartObject(XmlWriter writer, object graph)
{
CheckNull(writer, "writer");
this.WriteStartObject(XmlDictionaryWriter.CreateDictionaryWriter(writer), graph);
}
}
我們可以再服務操作裡使用數據契約類型參數或者返回此類型的結果,WCF框架會自動使用DataContractSerializer對參數或者結果進行序列化合反序列化。.NET 的內建基本類型如String、int等默認具有數據契約的特性,支持公開的標准,因此都是可以序列化的。
【4】代碼實現與分析:
下面是示例代碼的具體實現過程,這裡做簡要的講解.
【4.1】數據契約:
我們定義了一個數據契約類UserDataContract,包含簡單的幾個屬性Name、Email、Mobile,分別用來存儲用戶名、電子郵件、手機信息,代碼如下:
[DataContract]//數據契約屬性聲明
class UserDataContract
{
[DataMember(Name = "UserName")]//數據成員標記,支持別名定義
public string Name
{
get;set;
}
[DataMember(Name = "UserEmail")]//數據成員標記,支持別名定義
public string Email
{
get;set;
}
[DataMember]//數據成員標記
public string Mobile
{
get;set;
}
//沒有[DataMember]聲明,不會被序列化
public string Address
{
get; set;
}
}
【4.2】服務契約:
服務契約我們定義裡連個操作AddNewUser(UserDataContract user)和UserDataContract GetUserByName(string name);分別為了測試數據契約的序列化和傳遞特性,服務類裡給出了簡單的實現,實際項目應用過程中,我們可以訪問數據庫進行數據持久化操作,或者調用封裝其他的業務邏輯。代碼如下:
//1.服務契約
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
public interface IWCFService
{
//操作契約
[OperationContract]
bool AddNewUser(UserDataContract user);
//操作契約
[OperationContract]
UserDataContract GetUserByName(string name);
}
//2.服務類,繼承接口。實現契約
public class WCFService : IWCFService
{
//實現接口定義的方法
public bool AddNewUser(UserDataContract user)
{
//這裡可以定義數據持久化操作,訪問數據庫等
//不給出具體代碼
Console.WriteLine("Hello! ,This an DataContract demo for WCF Service ");
return true;
}
//實現接口定義的方法
public UserDataContract GetUserByName(string name)
{
UserDataContract userDataContract = new UserDataContract();
userDataContract.Address = "ShangHai China";
userDataContract.Email = "[email protected]";
userDataContract.Name = "Frank Xu Lei";
Console.WriteLine("Hello! {0},This an overloading demo WCF Service ", name);
return userDataContract;
}
}
【4.3】客戶端:
托管宿主的配置前面已經介紹過具體的配置過程,這裡不再詳述。客戶端添加服務引用,反序列化生成的客戶端數據契約代碼如下:
public partial class UserDataContract : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
[System.NonSerializedAttribute()]
private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
[System.Runtime.Serialization.OptionalFieldAttribute()]
private string MobileField;
[System.Runtime.Serialization.OptionalFieldAttribute()]
private string UserEmailField;
[System.Runtime.Serialization.OptionalFieldAttribute()]
private string UserNameField;
[global::System.ComponentModel.BrowsableAttribute(false)]
public System.Runtime.Serialization.ExtensionDataObject ExtensionData {
get {
return this.extensionDataField;
}
set {
this.extensionDataField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string Mobile {
get {
return this.MobileField;
}
set {
if ((object.ReferenceEquals(this.MobileField, value) != true)) {
this.MobileField = value;
this.RaisePropertyChanged("Mobile");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string UserEmail {
get {
return this.UserEmailField;
}
set {
if ((object.ReferenceEquals(this.UserEmailField, value) != true)) {
this.UserEmailField = value;
this.RaisePropertyChanged("UserEmail");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string UserName {
get {
return this.UserNameField;
}
set {
if ((object.ReferenceEquals(this.UserNameField, value) != true)) {
this.UserNameField = value;
this.RaisePropertyChanged("UserName");
}
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
我們可以看到客戶端數據契約的屬性都添加了[System.Runtime.Serialization.DataMemberAttribute()]標記,並且我們在服務端數據契約使用別名屬性客戶端也做了相應的調整。並且生成了相應的私有數據成員字段。
客戶端編寫代碼進行測試,分別測試增加客戶端傳遞數據契約對象和從服務端返回數據契約對象,代碼如下:
class WCFClient
{
static void Main(string[] args)
{
//實例化客戶端服務代理Tcp
WCFServiceClient wcfServiceProxy =
new WCFServiceClient("WCFDataContractFormatting.IWCFService");
Console.WriteLine("Test call service using TCP--------------------.");
//通過代理調用服務,分別傳遞不同的參數,進行測試
//實例化數據契約對象,設置信息
UserDataContract userDataContract = new UserDataContract();
userDataContract.UserName = "WCF Client: Frank Xu Lei";
userDataContract.UserEmail = "WCF Client: [email protected] ";
userDataContract.Mobile = "WCF Client:1666666666";
//調用代理服務,增加用戶操作:
wcfServiceProxy.AddNewUser(userDataContract);
//查詢用戶信息:
string name = "WCF Client: Frank Xu";
UserDataContract userData = wcfServiceProxy.GetUserByName(name);
if (userData != null)
{
Console.WriteLine(userData.UserName);
Console.WriteLine(userData.UserEmail);
Console.WriteLine(userData.Mobile);
}
//Debuging
Console.WriteLine("Press any key to continue");
Console.Read();
}
}
}
顯示結果正確,通過數據契約實現了客戶端與WCF服務端的信息交互。如下圖:
【5】總結
以上就是本節的全部內容。數據契約的設計和使用在WCF面向服務的分布式應用系統開發中有重要的意義。全文首先介紹了數據契約的基本概念,然後對現有的三種.NET 序列化機制進行了簡單的介紹和對比,指出其相關特性。然後對WCF序列化機制做了詳細介紹,WCF開發自己的面相服務的格式化器是因為現有的.NET 格式化器的功能上的局限性。最後給出了在WCF服務中使用數據契約進行服務操作的代碼實現,並給出了相應的分析。
本文配套源碼