跟我一起学WCF(7)——WCF数据契约与序列化详解

时间:2022-06-23 01:29:54

一、引言

  在前面博文介绍到,WCF的契约包括操作契约、数据契约、消息契约和错误契约,前面一篇博文已经结束了操作契约的介绍,接下来自然就是介绍数据契约了。所以本文要分享的内容就是数据契约。

二、数据契约的介绍

  在WCF中,服务契约定义了可供调用的服务操作方法,而数据契约则是定义了服务端和客户端之间传送的自定义类型,在WCF项目中,必不可少地是传递数据,把客户端需要传递的数据传送到服务中,服务接收到数据再对其进行处理。然而在WCF中,传递的类型必须标记为DataContractAttribute属性,且只有标记了DataMemberAttribute属性的属性才会被传送。下面代码是一个数据契约使用的示例:

 [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; }
}

  上面代码在类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 = "", Email = "123456@qq.com", Mobile = "" };
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 = "" };
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的调用也是类似的。具体的运行结果如下图所示:

跟我一起学WCF(7)——WCF数据契约与序列化详解

五、已知类型(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区别与用法分析

跟我一起学WCF(7)——WCF数据契约与序列化详解

  好的博文记录:Create and Consume RESTFul Service in .NET Framework 4.0

  本文所有源代码下载:WCFDataContract.zip

跟我一起学WCF(7)——WCF数据契约与序列化详解的更多相关文章

  1. 重温WCF之数据契约和序列化(四)

    一.数据契约 1.使用数据协定可以灵活控制哪些成员应该被客户端识别. [DataContract] public class Employee { [DataMember] public string ...

  2. WCF分布式开发步步为赢&lpar;7&rpar;&colon;WCF数据契约与序列化

    本节继续学习WCF分布式开发步步为赢(7):WCF数据契约与序列化.数据契约是WCF应用程序开发中一个重要的概念,毫无疑问实现客户端与服务端数据契约的传递中序列化是非常重要的步骤.那么序列化是什么?为 ...

  3. 转:WCF传送二进制流数据基本实现步骤详解

    来自:http://developer.51cto.com/art/201002/185444.htm WCF传送二进制流数据基本实现步骤详解 2010-02-26 16:10 佚名 CSDN   W ...

  4. &lt&semi;转&gt&semi;ASP&period;NET学习笔记之MVC 3 数据验证 Model Validation 详解

    MVC 3 数据验证 Model Validation 详解  再附加一些比较好的验证详解:(以下均为引用) 1.asp.net mvc3 的数据验证(一) - zhangkai2237 - 博客园 ...

  5. Code First开发系列之管理数据库创建,填充种子数据以及LINQ操作详解

    返回<8天掌握EF的Code First开发>总目录 本篇目录 管理数据库创建 管理数据库连接 管理数据库初始化 填充种子数据 LINQ to Entities详解 什么是LINQ to ...

  6. 8天掌握EF的Code First开发系列之3 管理数据库创建,填充种子数据以及LINQ操作详解

    本文出自8天掌握EF的Code First开发系列,经过自己的实践整理出来. 本篇目录 管理数据库创建 管理数据库连接 管理数据库初始化 填充种子数据 LINQ to Entities详解 什么是LI ...

  7. &lbrack;转帖&rsqb;IP &sol;TCP协议及握手过程和数据包格式中级详解

    IP /TCP协议及握手过程和数据包格式中级详解 https://www.toutiao.com/a6665292902458982926/ 写的挺好的 其实 一直没闹明白 网络好 广播地址 还有 网 ...

  8. Oracle10g数据泵impdp参数详解--摘自网络

    Oracle10g数据泵impdp参数详解 2011-6-30 12:29:05 导入命令Impdp •      ATTACH 连接到现有作业, 例如 ATTACH [=作业名]. •      C ...

  9. python数据分析数据标准化及离散化详解

    python数据分析数据标准化及离散化详解 本文为大家分享了python数据分析数据标准化及离散化的具体内容,供大家参考,具体内容如下 标准化 1.离差标准化 是对原始数据的线性变换,使结果映射到[0 ...

随机推荐

  1. sparksql udf的运用----scala及python版(2016年7月17日前完成)

    问:udf在sparksql 里面的作用是什么呢? 答:oracle的存储过程会有用到定义函数,那么现在udf就相当于一个在sparksql用到的函数定义: 第二个问题udf是怎么实现的呢? regi ...

  2. log4net Tutorial

    Introduction One of the greatest logging tools out there for .NET is log4net. This software is the g ...

  3. js动态生成数据列表

    我们通常会使用table标签来展示数据内容,由于需要展示的数据内容是随时更换的,所以不可能将展示的数据列表写死在html写死在页面中,而是需要我们根据后台传来的数据随时更换,这个时候就需要我们使用js ...

  4. FastDFS安装配置手册

    文件服务器分布式系统安装手册 本文档详细的介绍了FastDFS的最小集群安装过程.集群环境如下: tracker:20.2.64.133 .用于调度工作,在访问上起负载均衡的作用. group1: s ...

  5. 创建服务类PO

    转载:https://blogs.sap.com/2014/03/04/creating-a-simple-service-po-using-bapipocreate1bapipochange/ Cr ...

  6. 理解zookeeper选举机制

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  7. MySQL 开启慢查询日志

    1.1 简介 开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能. 1.2 登录数据库查看 [root@localhost lib]# ...

  8. 【2016北京集训测试赛(十六)】 River (最大流)

    Description  Special Judge Hint 注意是全程不能经过两个相同的景点,并且一天的开始和结束不能用同样的交通方式. 题解 题目大意:给定两组点,每组有$n$个点,有若干条跨组 ...

  9. centos6&period;5-vsftp搭建

    我的机子是默认是没有的vsftp. yum install -y vsftp 创建账户专为ftp而生.useradd ftp01 更改账户不可登录系统.usermod -s /sbin/nologin ...

  10. MSF内网渗透 扫描模块

    端口扫描 auxiliary/scanner/portscan scanner/portscan/ack        ACK防火墙扫描 scanner/portscan/ftpbounce  FTP ...