跟我一起學WCF(7)——WCF數據契約與序列化詳解


一、引言

   在前面博文介紹到,WCF的契約包括操作契約、數據契約、消息契約和錯誤契約,前面一篇博文已經結束了操作契約的介紹,接下來自然就是介紹數據契約了。所以本文要分享的內容就是數據契約。

二、數據契約的介紹

   在WCF中,服務契約定義了可供調用的服務操作方法,而數據契約則是定義了服務端和客戶端之間傳送的自定義類型,在WCF項目中,必不可少地是傳遞數據,把客戶端需要傳遞的數據傳送到服務中,服務接收到數據再對其進行處理。然而在WCF中,傳遞的類型必須標記為DataContractAttribute屬性,且只有標記了DataMemberAttribute屬性的屬性才會被傳送。下面代碼是一個數據契約使用的示例:

 1 [DataContract] // 數據契約屬性聲明
 2     public class User
 3     {
 4         [DataMember(Name = "UserName")]//定義別名
 5         public string Name
 6         { get; set; }
 7         [DataMember]
 8         public string Password { get; set; }
 9         [DataMember]
10         public string Email { get; set; }
11 
12         // 沒有[DataMember]聲明將不會序列化傳送
13         public string Mobile { get; set; } 
14 
15         public string Test { get; set; }
16     }

  上面代碼在類User上使用了DataContract屬性聲明,則表明User類是可被WCF序列化程序可識別,並且可被序列化的。但是不是User所有數據成員都可以被需要列,只有聲明了DataMemberAttribute的屬性才可以被序列化。因此,在上面代碼中,不會傳輸Mobile和Test的任何信息。同時也可以為聲明為DataMember的成員定義客戶端可見的別名,如DataMember(Name= "UserName"),這樣在生成客戶端代碼時,User類定義的就是UserName屬性,而不是在服務中定義的Name屬性了。

三、序列化的詳細介紹

  WCF的實現原理沿用了.NET Remoting的實現機制,客戶端在調用服務公開的服務方法,這個過程必然涉及到數據的傳輸過程,包括客戶端傳輸相關需要處理的數據給服務或服務傳輸相關處理后的結果數據給客戶端。在數據傳輸的過程中,自然就需要進行序列化的操作,通過序列化把.NET Object序列化成可保存或傳輸的形式,然后通過網絡協議在網絡上進行傳遞。對於序列化的實現是由序列化器(Serializer)來負責完成的,序列化的實現原理可以理解為通過反射機制分析程序集中對應的類型,然后把對應的類型映射為一個XML的結構。

  序列化在.NET Framework相關專題就有所介紹,所以它並不是一個新的概念,相關內容可以參考MSDN:序列化。然而.NET本身的序列化機制在WCF程序中並不適應,所以WCF又提出了新的序列化器。下面分別介紹下.NET 序列化機制和WCF中序列化機制。

3.1 .NET序列化機制

   在.NET Framework 3.0之前,提供了3中序列化器,序列化器理解為把可序列化的類型序列化成XML的類。這三種序列化器分別是BinaryFormatterSoapFormatterXmlSerializer類。下面分別介紹下這3種序列化器。

  • BinaryFormatter類:把.NET Object序列化成二進制格式。在這個過程,對象的公共字段和私有字段以及類名稱(包括類的程序集名),將轉換成成字節流。
  • SoapFormatter類:把.NET Object序列化成SOAP格式,SOAP是一種輕量、簡單的,基於XML的協議。只序列化字段,包括公共字段和私有字段
  • XmlSerializer類:該類僅僅序列化公共字段和屬性,且不保存類型的保真度。

  對於這三種序列化機制,BinaryFormatter二進制序列化的優點是:性能高,但是不能跨平台。而SoapFormatter,XmlSerializer的優點是:跨平台、互操作性好,並且可讀性強,但是傳輸性能不及BinaryFormatter。

  在.NET原有的序列化機制中,BinaryFormatter和SoapFormatter除了要序列化對象的狀態信息外,還會將程序集和版本信息持久化到流中,因為只有這樣才能保證對象唄反序列為正確的對象類型副本,這就要求客戶端必須擁有原有的.NET 程序集,不能滿足跨平台的需求。所以WCF不得不定義自己的序列化機制來滿足面向服務的需求。

3.2 WCF中序列化機制

   在WCF中,提供了專門用來序列化和反序列操作的類,該類就是DataContractSerializer類。一般而言,WCF會自動選擇使用DataContractSerializer來對可序列話數據契約進行序列化,不需要開發者直接調用。WCF除了支持DataContractSerializer類來進行序列化外,還支持另外兩種序列化器,這兩種序列化器分別為:XMLSerializer(定義在System.XML.Serialization namespace)和NetDataContractSerializer (定義在System.XML.Serialization namespace)。XmlSerializer類不是WCF專用的類,Asp.net Web服務統一使用該類作為序列化器,但XmlSerializer類支持的類少於DataContractSerializer列支持的類型,但它允許對生成的XML進行更多的控制,並且支持更多的XML架構定義語言(XSD)標准。它不需要在可序列化類上有任何聲明性的屬性。

  默認情況下,WCF 使用 DataContractSerializer 類來序列化數據類型。 此序列化程序支持下列類型:

  DataContractSerializer類與NetDataContractSerializer類類似,它們之間主要的區別在於:在使用NetDataContractSerializer進行序列化時,不需要指定序列化的類型,如:

NetDataContractSerializer serializer =
    new NetDataContractSerializer();  // 不需要明確指定序列化的類型
serializer.WriteObject(writer, p);

// 而使用DataContractSerializer需要明確指定序列化的類型
DataContractSerializer serializer =
            new DataContractSerializer(typeof(Order)); // 需要明確指定序列化的類型
        serializer.WriteObject(writer, p);

四、WCF數據契約使用例子

   介紹了那么多關於數據契約和序列化內容的介紹,下面看看數據契約具體使用的例子。

  要使用數據契約,自然第一步是定義數據契約,具體數據契約的定義如下所示:

namespace BusinessEntity
{
    [DataContract]// 數據契約屬性聲明
    public class User
    {
        [DataMember(Name = "UserName")]//定義別名
        public string Name
        { get; set; }
        [DataMember]
        public string Password { get; set; }
        [DataMember]
        public string Email { get; set; }

        // 沒有[DataMember]聲明將不會序列化傳送
        public string Mobile { get; set; } 

        public string Test { get; set; }
    }
}

  第二步:定義完數據契約后,接下來就要定義我們的服務契約和服務契約的實現了。具體的實現代碼如下所示:

 // 服務契約
    [ServiceContract]
    //[ServiceKnownType(typeof(Order))] // 這是為了演示WCF已知類型
    public interface IUserValidationService
    {
        [OperationContract]
        bool AddNewUser(User user);

        [OperationContract]
        User GetUserByName(string name);
        
        // 為了演示已知類型的操作方法
        //[OperationContract]
        //[ServiceKnownType(typeof(Order))]
        //bool AddOrder(OrderBase order);
    }
 // 服務契約的實現
    public class UserValidationService : IUserValidationService
    {
        public bool AddNewUser(User user)
        {
            return true;
        }

        public User GetUserByName(string name)
        {
            User user = new User { Name = name, Password = "123", Email = "123456@qq.com", Mobile = "13912331245" };
            return user;
        }

        // 演示已知類型的操作方法
        //public bool AddOrder(OrderBase order)
        //{
        //    return true;
        //}
    }

  對應的配置文件代碼為:

 <system.serviceModel>
      
    <behaviors>
      <serviceBehaviors>
        <behavior name="UserServiceBehavior">
          <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
          
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
        <add binding="basicHttpsBinding" scheme="https" />
    </protocolMapping>    
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />

      <services>
          <service name="WCFServiceAndHost.UserValidationService" behaviorConfiguration="UserServiceBehavior">
              <endpoint address="" binding="wsHttpBinding" contract="WCFServiceAndHost.IUserValidationService"></endpoint>
          </service>
      </services>
  </system.serviceModel>

  第三步:定義完服務之后,接下來就需要實現我們的客戶端來訪問服務方法了。首先,通過添加服務引用的方式來生成服務客戶端代理類,生成的代理類中,User的定義如下代碼所示:

[System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
    [System.Runtime.Serialization.DataContractAttribute(Name="User", Namespace="http://schemas.datacontract.org/2004/07/BusinessEntity")]
    [System.SerializableAttribute()]
    public partial class User : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged 
{
....
}

  從上面代碼標紅的部分可以看出,服務中定義的User只應用了DataContractAttribute屬性,但生成的客戶端User類中多了一個SerializableAtttribute。對於SerializableAttribute屬性的作用與DataContract的作用是一樣的,都是標記為該類支持序列化。因為在默認情況下,用戶定義的類型並不支持序列化,只有應用了SerializableAttribute或DataContractAttribute屬性的,.NET序列化器才能對該類型進行序列化。然而這兩者又存在不同, Serializable要求它的所有程序都要支持序列化,如果發現不支持序列化的成員就會拋出異常,即Serializable會把類型的所有成員都進行序列化,如果想某個成員不序列化化,則必須顯式標記NoSerialized屬性;而DataContract卻不同,標記了DataContract屬性的類只有標記了DataMember的成員才會被序列化,如果想類型的成員能夠序列化,則應該應用DataMember屬性。如果某個類型同時應用了DataContract和Serialized屬性,如上面代碼的User類,此時該類型將會只應用DataContract,即Serialized屬性會忽略。我剛開始的疑問是,User類應用這兩個屬性,因為這兩個屬性對序列化成員有所區別,當時就納悶到底是采取那個屬性進行序列化的呢?經過查閱資料才發現了上面的結論,更多信息參考:Serialization in Windows Communication Foundation

  然后利用該代理類實現對服務操作的調用,具體的實現代碼如下所示:

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            UserValidationServiceClient wcfServiceProxy = new UserValidationServiceClient();
            User newUser = new User() { UserName = "LearningHard", Email = "123456@qq.com", Password = "123" };
            wcfServiceProxy.AddNewUser(newUser);

            // 演示已知類型的問題
            //Order order = new Order() { ID = Guid.NewGuid(), Date = DateTime.Now, Customer = "customer1", ShipAddress = "Shanghai", TotalPrice = 20.00 };
            //wcfServiceProxy.AddOrder(order);

            // 獲得用戶信息
            string name = "Learning Hard Client";
            User user = wcfServiceProxy.GetUserByName(name);
            if (user != null)
            {
                Console.WriteLine("User Name is: " + user.UserName);
                Console.WriteLine("Email is: " + user.Email);
            }

            Console.WriteLine("Press any key to continue...");
            Console.Read();
        }
    }
}

  經過上面的三步之后,我們就完成了WCF數據契約的實現。對於服務契約的調用過程是:客戶端把相關需要序列化的對象序列化成XML格式,這里的格式與綁定的協議有關,因為上面設置的傳輸協議為http,所以這里應該序列化成XML格式的數據,然后再通過Http協議進行網絡傳遞到服務,服務程序接收到傳輸過來的XML格式的數據,則利用DataContractSerializer反序列成User對象作為參數傳遞給AddNewUser方法;接着服務再把處理的后結果序列化成XML格式數據傳遞到客戶端,客戶端接收到服務程序響應的消息再進行反序列成具體的對象類型。對於操作GetUserByName的調用也是類似的。具體的運行結果如下圖所示:

 

五、已知類型(KnownType)

   因為WCF中使用DataContractSerializer進行序列化和反序列化的,由於DataContractSerializer進行序列化和反序列化時,都必須事先確定對象的類型。如果被序列化對象或反序列化生成的對象包含不可知的類型,序列化或反序列化將失敗。所以為了保證DataContractSerializer正常的序列化和反序列化,需要將“未知”類型加入DataContractSerializer“已知”類型列表中。例如下面的服務契約:

// 服務契約
    [ServiceContract]
    //[ServiceKnownType(typeof(Order))] // 這是為了演示WCF已知類型
    public interface IUserValidationService
    {
        // 為了演示已知類型的操作方法
        [OperationContract]
        [ServiceKnownType(typeof(Order))]
        bool AddOrder(OrderBase order);
    }

  假如,客戶端同時定義了一個Order類:

[DataContract]
    public class Order : OrderBase
    {
        [DataMember]
        public double TotalPrice { get; set; }
    }

  以下代碼能夠成功通過編譯,但在運行時卻會失敗:

 UserValidationServiceClient wcfServiceProxy = new UserValidationServiceClient();
            // 演示已知類型的問題
            Order order = new Order() { ID = Guid.NewGuid(), Date = DateTime.Now, Customer = "customer1", ShipAddress = "Shanghai", TotalPrice = 20.00 };
            wcfServiceProxy.AddOrder(order);

  原因在於我們並沒有實際傳遞對象的引用,而是傳遞的是對象的XML結構。在上面的例子中,當我們傳遞的是Order對象而不是OrderBase對象時,服務並不知道它應該反序列為Order對象。

  對於上面問題的解決辦法就是讓DataContractSerializer能夠識別Order類型,成為DataContractSerializer的已知類型(Known Type)。DataContractSerializer內部具有一個已知類型的列表,我們需要將Order類型添加到這個列表中。對於已知類型,可以通過兩個特性設置:KnownTypeAttribute和ServiceKnownTypeAttribute。KnownTypeAttribute應用於數據契約中,用於設置繼承於該數據契約類型的子數據契約,或引用其他的契約類型。ServiceKnownTypeAttribute既可以應用於服務契約的接口和方法上,還可以應用在服務實現的類和方法上,應用在不同的目標元素,決定了定義已知類型的作用范圍,下面,通過在基類OrderBase指定了子契約的類型Order:

   [DataContract]
    [KnownType(typeof(Order))]
    public abstract class OrderBase : IOrder
    { ... }

  而ServiceKnownTypeAttribute特性,不僅可以使用在服務契約類型上,還可以應用在服務契約的操作方法上。如果應用在服務契約類型上,則已知類型在所有實現了該契約的服務操作中都有效,即作用范圍為服務契約界別的,如果應用於服務契約的操作方法上,則定義的已知類型僅在實現了該契約的服務操作中有效。

  // 服務契約
    [ServiceContract]
    [ServiceKnownType(typeof(Order))]  // 服務契約級別
    public interface IUserValidationService
    {   
        // 為了演示已知類型的操作方法
        //[OperationContract]
        //[ServiceKnownType(typeof(Order))] // 單個服務操作級別
        bool AddOrder(OrderBase order);
    }

  除了通過特性的方式設置已知類型外,還可以通過配置文件的方式來進行指定。已知類型定義在<System.runtime.serialization>配置節點中,可以采用下面的方式來定義:

<configuration>
  <system.runtime.serialization>
    <dataContractSerializer>
      <declaredTypes>
        <add type="BusinessEntity.OrderBase,BusinessEntity.KnownTypes">
          <knownType type="BusinessEntity.Order,BusinessEntity.KnownTypes"/>
        </add> 
      </declaredTypes>
    </dataContractSerializer>
  </system.runtime.serialization>
</configuration>

六、總結

  到這里,數據契約的分享就結束。對於這篇博文首先介紹了數據契約和序列化的基本知識,接着介紹了.NET中的序列化機制和WCF中序列化機制,最后完成了一個數據契約的例子。看完本篇文章應該明確幾個問題:

  1. SerializableAttribute與DataContract異同。

答:相同點:都是標記類型為可序列化類型

  不同點:在於序列化的成員不一樣,DataContract是Opt-in(明確參與)的方式,即使用DataMember特性明確標識哪些成員需要序列化,而Serializable是Opt-out方式,即使用NoSerializable特性明確標識不參與序列化的成員。

  2. BinartFormatter、DataContractSerializer和XmlSerializer的區別,具體答案見下圖和參考下面博文:XmlSerializer, DataContractSerializer 和 BinaryFormatter區別與用法分析

  好的博文記錄:Create and Consume RESTFul Service in .NET Framework 4.0

  本文所有源代碼下載:WCFDataContract.zip

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM