WCF初探-16:WCF数据协定之基础知识
数据协定概念
- “数据协定”是在服务与客户端之间达成的正式协议,用于以抽象方式描述要交换的数据。 也就是说,为了进行通信,客户端和服务不必共享相同的类型,而只需共享相同的数据协定。 数据协定为每一个做数据交换时需要被序列化的参数或者返回值做了精确定义。
数据协定特点
- 默认情况下, 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的一些默认特点。