數據協定概念
- “數據協定”是在服務與客戶端之間達成的正式協議,用於以抽象方式描述要交換的數據。 也就是說,為了進行通信,客戶端和服務不必共享相同的類型,而只需共享相同的數據協定。 數據協定為每一個做數據交換時需要被序列化的參數或者返回值做了精確定義。
數據協定特點
- 默認情況下, WCF使用稱為數據協定序列化程序的序列化引擎對數據進行序列化和反序列化(與 XML 進行相互轉換)。 所有 .NET Framework 基元類型(如整型和字符串型)以及某些被視為基元的類型(如 DateTime 和 XmlElement)無需做其他任何准備工作就可序列化並被視為擁有默認數據協定。創建數據協定時需要考慮的事項:
- 僅當用於未標記的類型時,才接受 IgnoreDataMemberAttribute 屬性。 這包括未使用 DataContractAttribute、SerializableAttribute、CollectionDataContractAttribute 或 EnumMemberAttribute 屬性之一標記的類型,或通過任何其他方式(如 IXmlSerializable)標記為可序列化的類型。
- 可以將 DataMemberAttribute 屬性 (Attribute) 應用於字段和屬性 (Property)。
- 成員可訪問性級別(internal、private、protected 或 public)對數據協定無任何影響。
- 如果將 DataMemberAttribute 屬性應用於靜態成員,則將忽略該屬性。
- 在序列化期間,為屬性數據成員調用 property-get 代碼來獲取要序列化的屬性的值。
- 在反序列化期間,首先創建一個未初始化的對象,而不在該類型上調用任何構造函數。 然后反序列化所有數據成員。
- 在反序列化期間,為屬性數據成員調用 property-set 代碼,將屬性設置為要反序列化的值。
- 對於將要生效的數據協定,它必須能序列化其所有數據成員。
- 創建類或結構的基本數據協定時,通過將 DataContractAttribute 屬性應用於類來聲明該類型具有數據協定。通過將 DataMemberAttribute 屬性 (Attribute) 應用於每個成員來定義要序列化的成員(屬性 (Property)、字段或事件)。但是,即使未用這些屬性進行標記的類型也會進行序列化和反序列化。 適用的規則和例外如下:
- DataContractSerializer 使用新創建的類型的默認屬性 (Property) 從不帶屬性 (Attribute) 的類型推斷數據協定。
- 除非對成員應用 IgnoreDataMemberAttribute 屬性 (Attribute),否則所有公共字段以及具有公共 get 和 set 方法的屬性 (Property) 都會序列化。
- 序列化語義與 XmlSerializer 的語義類似。
- 在未標記的類型中,僅序列化具有不帶參數的構造函數的公共類型。 此規則的例外是用於 IExtensibleDataObject 接口的 ExtensionDataObject。
- 只讀字段、沒有 get 或 set 方法的屬性以及具有內部或私有 set 或 get 方法的屬性不會進行序列化。 此類屬性會被忽略,但不會引發異常(get-only 集合的情況除外)。
- 會忽略 XmlSerializer 屬性(如 XmlElement、XmlAttribute、XmlIgnore、XmlInclude 等)。
- 如果未將 DataContractAttribute 屬性應用於某個給定類型,則序列化程序會忽略該類型中應用了 DataMemberAttribute 屬性的所有成員。
- 未使用 DataContractAttribute 屬性 (Attribute) 進行標記的類型中支持 KnownTypes 屬性 (Property)。 這包括對未標記類型上的 KnownTypeAttribute 屬性 (Attribute) 的支持。
- 若要使公共成員、屬性 (Property) 或字段“退出”序列化過程,請向該成員應用 IgnoreDataMemberAttribute 屬性 (Attribute)。
- 在一些應用程序中,有必要知道各個數據成員中數據的發送順序或預期接收順序(比如序列化 XML 中數據的顯示順序)。 有時,必須要更改此順序。數據排序的基本規則包括:
- 如果數據協定類型是繼承層次結構的一部分,則其基類型的數據成員始終排在第一位。
- 排在下一位的是當前類型的數據成員(按字母順序排列),這些成員未設置 DataMemberAttribute 屬性 (attribute) 的 Order 屬性 (property)。
- 再下面是設置了 DataMemberAttribute 屬性 (attribute) 的 Order 屬性 (property) 的任何數據成員。 這些成員首先按 Order 屬性的值排序,如果多個成員具有特定的 Order 值,則按字母順序排列。 可以跳過 Order 值。
- 給定類型的默認數據協定名稱是該類型的名稱。 若要重寫默認值,請將 DataContractAttribute 的 Name 屬性設置為其他名稱。給定字段或屬性的默認數據成員名稱是該字段或屬性的名稱。 若要重寫默認值,請將 DataMemberAttribute 的 Name 屬性設置為其他值。數據協定命名空間采用統一資源標識符 (URI) 的形式。 URI 可以是絕對的,也可以是相對的。 默認情況下,會為特定類型的數據協定分配公共語言運行庫 (CLR) 命名空間中該類型的命名空間。數據協定命名的基本規則包括:
- 完全限定的數據協定名稱由命名空間和名稱組成。
- 數據成員只有名稱,而沒有命名空間。
- 處理數據協定時,WCF 基礎結構對於命名空間以及數據協定和數據成員的名稱區分大小寫。
- 默認情況下,任何給定的 CLR 命名空間(采用 Clr.Namespace 格式)都會映射到“http://schemas.datacontract.org/2004/07/Clr.Namespace”命名空間。 若要重寫此默認值,請對整個模塊或程序集應用 ContractNamespaceAttribute 屬性。 或者,若要控制每種類型的數據協定命名空間,請設置 DataContractAttribute 的 Namespace 屬性。
數據協定示例
- 解決方案如下:

- 工程結構說明如下:
- Service:類庫類型,WCF服務端程序。定義服務契約接口IUserInfo,定義操作契約方法GetInfo獲取用戶信息,定義數據契約User,提供用戶信息的傳輸對象。IUserInfo.cs的代碼如下:
using System.ServiceModel; using System.Runtime.Serialization; namespace Service { [ServiceContract] public interface IUserInfo { [OperationContract] User[] GetInfo(); } [DataContract(Name="DCUser",Namespace="http://wangweimutou.DCUser")] public class User { [DataMember(Order=1)] public int ID { get; set; } [DataMember(Name="姓名",Order=2)] public string Name { get; set; } [DataMember] private int Age; private string Address; [DataMember] public string Email { get; set; } [IgnoreDataMember] public string Phone { get; set; } private string _job; [DataMember] public string Job { get { return _job; } set { _job = value; } } public string Nationality { get; set; } } }
UserInfo.cs的代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Service { public class UserInfo:IUserInfo { public User[] GetInfo() { List<User> listData = new List<User>(); return listData.ToArray(); } } }
2. Host:控制台應用程序,服務承載程序。添加對Service程序集的引用,Program.cs的代碼如下:
using System; using System.ServiceModel; using Service; namespace Host { class Program { static void Main(string[] args) { using (ServiceHost host = new ServiceHost(typeof(UserInfo))) { host.Opened += delegate { Console.WriteLine("服務已經啟動,按任意鍵終止!"); }; host.Open(); Console.Read(); } } } }
App.config的代碼如下:
<?xml version="1.0"?> <configuration> <system.serviceModel> <services> <service name="Service.UserInfo" behaviorConfiguration="mexBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:1234/UserInfo/"/> </baseAddresses> </host> <endpoint address="" binding="wsHttpBinding" contract="Service.IUserInfo" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="mexBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
3. Client:控制台引用程序,啟動Host程序寄宿服務,在命令行中輸入以下命令,將生成的UserInfoClient.cs和App.config文件拷貝到Client程序目錄下。

UserInfoClient.cs代碼如下:
//------------------------------------------------------------------------------ // <auto-generated> // 此代碼由工具生成。 // 運行時版本:2.0.50727.5485 // // 對此文件的更改可能會導致不正確的行為,並且如果 // 重新生成代碼,這些更改將會丟失。 // </auto-generated> //------------------------------------------------------------------------------ [assembly: System.Runtime.Serialization.ContractNamespaceAttribute("http://wangweimutou.DCUser", ClrNamespace="wangweimutou.dcuser")] namespace wangweimutou.dcuser { using System.Runtime.Serialization; [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")] [System.Runtime.Serialization.DataContractAttribute(Name="DCUser", Namespace="http://wangweimutou.DCUser")] public partial class DCUser : object, System.Runtime.Serialization.IExtensibleDataObject { private System.Runtime.Serialization.ExtensionDataObject extensionDataField; private int AgeField; private string EmailField; private string JobField; private int IDField; private string 姓名Field; public System.Runtime.Serialization.ExtensionDataObject ExtensionData { get { return this.extensionDataField; } set { this.extensionDataField = value; } } [System.Runtime.Serialization.DataMemberAttribute()] public int Age { get { return this.AgeField; } set { this.AgeField = value; } } [System.Runtime.Serialization.DataMemberAttribute()] public string Email { get { return this.EmailField; } set { this.EmailField = value; } } [System.Runtime.Serialization.DataMemberAttribute()] public string Job { get { return this.JobField; } set { this.JobField = value; } } [System.Runtime.Serialization.DataMemberAttribute(Order=3)] public int ID { get { return this.IDField; } set { this.IDField = value; } } [System.Runtime.Serialization.DataMemberAttribute(Order=4)] public string 姓名 { get { return this.姓名Field; } set { this.姓名Field = value; } } } } [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] [System.ServiceModel.ServiceContractAttribute(ConfigurationName="IUserInfo")] public interface IUserInfo { [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IUserInfo/GetInfo", ReplyAction="http://tempuri.org/IUserInfo/GetInfoResponse")] wangweimutou.dcuser.DCUser[] GetInfo(); } [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] public interface IUserInfoChannel : IUserInfo, System.ServiceModel.IClientChannel { } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] public partial class UserInfoClient : System.ServiceModel.ClientBase<IUserInfo>, IUserInfo { public UserInfoClient() { } public UserInfoClient(string endpointConfigurationName) : base(endpointConfigurationName) { } public UserInfoClient(string endpointConfigurationName, string remoteAddress) : base(endpointConfigurationName, remoteAddress) { } public UserInfoClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : base(endpointConfigurationName, remoteAddress) { } public UserInfoClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : base(binding, remoteAddress) { } public wangweimutou.dcuser.DCUser[] GetInfo() { return base.Channel.GetInfo(); } }
客戶端數據契約解析
- 我們進UserInfoClient.cs中生成的數據契約代碼提取出來,代碼顯示如下:
[assembly: System.Runtime.Serialization.ContractNamespaceAttribute("http://wangweimutou.DCUser", ClrNamespace="wangweimutou.dcuser")] namespace wangweimutou.dcuser { using System.Runtime.Serialization; [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")] [System.Runtime.Serialization.DataContractAttribute(Name="DCUser", Namespace="http://wangweimutou.DCUser")] public partial class DCUser : object, System.Runtime.Serialization.IExtensibleDataObject { private System.Runtime.Serialization.ExtensionDataObject extensionDataField; private int AgeField; private string EmailField; private string JobField; private int IDField; private string 姓名Field; public System.Runtime.Serialization.ExtensionDataObject ExtensionData { get{ return this.extensionDataField;} set{this.extensionDataField = value;} } [System.Runtime.Serialization.DataMemberAttribute()] public int Age { get{return this.AgeField;} set{this.AgeField = value;} } [System.Runtime.Serialization.DataMemberAttribute()] public string Email { get{return this.EmailField;} set{this.EmailField = value;} } [System.Runtime.Serialization.DataMemberAttribute()] public string Job { get{return this.JobField;} set{this.JobField = value;} } [System.Runtime.Serialization.DataMemberAttribute(Order=3)] public int ID { get{return this.IDField;} set{this.IDField = value;} } [System.Runtime.Serialization.DataMemberAttribute(Order=4)] public string 姓名 { get{return this.姓名Field;} set{this.姓名Field = value;} } } }
- 從上面的代碼可以看出我們的數據契約的Name變成了DCUser,Namespace變成了http://wangweimutou.DCUser,這和服務端的數據契約對應。
- 代理類中的數據契約生成的屬性有6個,服務端數據契約定義的屬性有8個,其中產生的差異,我們用以下圖形來說明:

總結
- 通過上面的示例,我們了解DataContract的序列化的順序和一些基本屬性設置,也了解到DataContract的一些默認特點。
