WCF技术剖析之十四:泛型数据契约和集合数据契约(上篇)

简介:

在.NET Framework 2.0中,泛型第一次被引入。我们可以定义泛型接口、泛型类型、泛型委托和泛型方法。序列化依赖于真实具体的类型,而泛型则刻意模糊了具体类型概念。而集合代表一组对象的组合,集合具有可迭代(Enumerable)的特性,可以通过某个迭代规则遍历集合中的每一个元素。由于范型类型和集合类型在序列化和反序列化上具有一些特殊的行为和规则,在这篇文章中,我将会对此进行详细介绍。上篇先来说所泛型数据契约。

一、泛型与数据契约

面向对象通过继承实现了代码的重用,而泛型则实现了“算法的重用”。我们定义一种算法,比如排序、搜索、交换、比较或者转换等等,为了实现尽可能的重用,我们并不限定该算法操作对象的具体类型,而通过一个泛型类型来表示。在真正创建范型对象或者调用该方法的时候,才指定其具体的类型。

就实现来说,泛型是CLR和编程语言(或者是基于编程语言的编译器)共同实现的一种特殊机制;就泛型的概念来说,这是面向对象的范畴。而我们现在介绍的数据契约,则属于面向服务的概念。两者具有一些冲突 ,比如面常服务没有继承、重载的概念一样,面向服务同样也无法理解泛型。

但是基于WCF的编程语言是C#、VB.NET这样的完全面向对象的编程语言,而WCF服务却是基于面向服务的。所以,从某种意义上讲,WCF的一个重大的作用就是弥合面向对象编程(OOP)和面向服务架构(SOA)之间的差异。我们现在就来看看WCF做了些什么使我们能够以泛型类型的形式来定义数据契约。

二、泛型数据契约的默认序列化规则

我们首先通过一个简单的例子看看DataContractSerializer是如何序列化一个范型对象的。为此我定义一个泛型类型Bill<BillHeader, BillDetail>,代表一个一般意义上的单据,BillHeader和BillDetail代表单据报头的明细的类型。两个属性Header和Details表示单据报头和明细列表。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Namespace="http://www.artech.com/")]
   4:     public class Bill<BillHeader, BillDetail>
   5:     {
   6:         [DataMember(Order = 1)]
   7:         public BillHeader Header
   8:         { get; set; }
   9:  
  10:         [DataMember(Order = 2)]
  11:         public BillDetail[] Details
  12:         { get; set; }
  13:     }
  14: }

然后我们定义用于描述订单单据的报头和明细的类型:OrderBillHeader和OrderBillDetail。OrderBillHeader描述定单的总体信息,OrderBillDetail实际上表示订单中每一个产品的ID、单价和数量。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Namespace="http://www.artech.com/")]
   4:     public class OrderBillHeader
   5:     {
   6:         [DataMember]
   7:         public Guid OrderID
   8:         { get; set; }
   9:  
  10:         [DataMember]
  11:         public DateTime Date
  12:         { get; set; }
  13:  
  14:         [DataMember]
  15:         public string Customer
  16:         { get; set; }
  17:     }
  18:  
  19:     [DataContract(Namespace="http://www.artech.com/")]
  20:     public class OrderBillDetail
  21:     {
  22:         [DataMember]
  23:         public Guid ProductID
  24:         { get; set; }
  25:  
  26:         [DataMember]
  27:         public int Quantity
  28:         { get; set; }
  29:  
  30:         [DataMember]
  31:         public double UnitPrice
  32:         { get; set; }
  33:     }
  34: }

通过下面一个方法创建泛型类型Bill<BillHeader, BillDetail>对象,泛型类型指定为上面定义的OrderBillHeader和OrderBillDetail。

   1: private static Bill<OrderBillHeader, OrderBillDetail> CreateOrderBill()
   2: {
   3:     OrderBillHeader header = new OrderBillHeader
   4:     {
   5:         OrderID     = Guid.NewGuid(),
   6:         Date         = DateTime.Today,
   7:         Customer     = "Foo"
   8:     };
   9:  
  10:     IList<OrderBillDetail> details = new List<OrderBillDetail>();
  11:     OrderBillDetail detail = new OrderBillDetail
  12:     {
  13:         ProductID     = Guid.NewGuid(),
  14:         Quantity     = 20,
  15:         UnitPrice     = 888
  16:     };
  17:     details.Add(detail);
  18:     detail = new OrderBillDetail
  19:     {
  20:         ProductID     = Guid.NewGuid(),
  21:         Quantity     = 10,
  22:         UnitPrice     = 9999
  23:     };
  24:     details.Add(detail);
  25:  
  26:  
  27:     Bill<OrderBillHeader, OrderBillDetail> orderBill = new Bill<OrderBillHeader, OrderBillDetail>()
  28:     {
  29:         Header     = header,
  30:         Details     = details.ToArray<OrderBillDetail>()
  31:     };
  32:     return orderBill;
  33: }

借助在《WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)》定义的Serialize<T>辅助方法,我们对创建Bill<OrderBillHeader, OrderBillDetail>对象进行序列化。最终对象将被序列化成如下的XML。

   1: Bill<OrderBillHeader, OrderBillDetail> orderBill = CreateOrderBill();
   2: Serialize<Bill<OrderBillHeader, OrderBillDetail>>(orderBill, @"orderbill.xml");
   1: <BillOfOrderBillHeaderOrderBillDetail6Of3LqKh xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.artech.com/Artech.DataContractSerializerDemos">
   2:     <Header>
   3:         <Customer>NCS</Customer>
   4:         <Date>2008-12-04T00:00:00+08:00</Date>
   5:         <OrderID>15a62aae-c955-4bc0-acb6-e171fb9fe085</OrderID>
   6:     </Header>
   7:     <Details>
   8:         <OrderBillDetail>
   9:             <ProductID>f7679949-938a-40a0-a32a-5dde5c85e55f</ProductID>
  10:             <Quantity>20</Quantity>
  11:             <UnitPrice>888</UnitPrice>
  12:         </OrderBillDetail>
  13:         <OrderBillDetail>
  14:             <ProductID>bbd750ff-8b0c-48f5-ab1f-5ad7e51bd420</ProductID>
  15:             <Quantity>10</Quantity>
  16:             <UnitPrice>9999</UnitPrice>
  17:         </OrderBillDetail>
  18:     </Details>
  19: </BillOfOrderBillHeaderOrderBillDetail6Of3LqKh>

XML整体的结构正是我们希望的,关键是根节点名称,也就是数据契约的名称,“BillOfOrderBillHeaderOrderBillDetail6Of3LqKh”,会让有些人难以理解。我们仔细分析一下数据契约的名称,会发现它的组成结构是这样的:{类型名称(Bill)}+ Of + {第一个范型参数的类型(OrderBillHeader)} + {第二个范型参数的类型(OrderBillDetail)}+ {哈希值(6Of3LqKh)}。

这里说泛型参数的类型,实际上是不对的,应该说OrderBillHeader和OrderBillDetail的泛型类型对应的数据契约的名称。在下面的代码中。通过 DataContractAttribute特性修改了数据契约的名称(OrderHeader和OrderDetail),最终的数据契约的名称将会变成:BillOfOrderHeaderOrderDetail6Of3LqKh。可以看出描述泛型数据契约的部分内容相应地改变了。可能仔细的读者已经发现了,哈希值部分却没有发生变化,依然是“6Of3LqKh”,这是因为这是泛型类型(含命名空间)的哈希值,而不是数据契约名称的哈希值。所以我们可以将默认的基于泛型类型的命名规则表示成:[类型名称][范型数据契约名称1][ 范型数据契约名称2][…][含命名空间的范型类型哈希值]。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Name="OrderHeader")]
   4:     public class OrderBillHeader
   5:     {
   6:         //省略成员
   7:     }
   8:  
   9:     [DataContract(Name = "OrderDetail")]
  10:     public class OrderBillDetail
  11:     {
  12:         //省略成员
  13:     }
  14: }

WCF之所以要采用这样的数据契约命名方式,是为了解决命名冲突,保证数据契约名称的唯一性。我们说了,面向服务下的数据契约完全没有泛型的概念,对它来说所有的类型都是“实实在在”的具体类型。对于泛型类型Bill<BillHeader,BillDetail>,不同的BillHeader和BillDetail组合代表不同的数据契约,所以最终的数据契约的名称需要由自身类型和泛型契约名称派生出来。由于在定义数据契约的时候,不同的CLR类型可以指定相同的数据契约名称,所以加上一个基于所有范型类型(含命名空间)的哈希值可以确保数据契约的唯一性。

WCF在进行元数据发布的时候,会自动按照这样的命名机制创建数据契约,并以XSD的形式发布出来。所以当客户端导入元数据生成客户端代码的时候,生成的等效数据契约的类型名称就是这个经过拼接的名称。下面是Bill<OrderBillHeader, OrderBillDetail>导入的形式。

   1: public partial class BillOfOrderBillHeaderOrderBillDetail6Of3LqKh : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged
   2: {
   3:     //省略成员    
   4: }

三、如何显式指定契约名称

如果你能够确保命名不会发生冲突,你可以通过DataContractAttribute特性的Name属性对数据契约的名称进行显式设置。比如在下面的代码中,将契约名称限定为“OrderBill”。不过这样设置就意味着你假定泛型类型只能表示基于订单的单据了,这相当于失去了泛型的意义。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Name="OrderBill")]
   4:     public class Bill<BillHeader, BillDetail>
   5:     {
   6:         //省略成员
   7:     }
   8: }

所以我们可以采用一种动态的设置方式,为数据契约的名称指定一个模板,使用表示泛型数据契约名称和泛型类型哈希值的占位符。其中{0}、{1}表示的是范型数据契约的名称,数字表示相应的范型参数出现的次序,而哈希值则通过{#}表示。所以下面两种范型数据契约是完全等效的。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract]
   4:     public class Bill<BillHeader, BillDetail>
   5:     {
   6:         //省略成员
   7:     }
   8: }
   9: namespace Artech.DataContractSerializerDemos
  10: {
  11:     DataContract(Name="BillOf{0}{1}{#}")]
  12:     public class Bill<BillHeader, BillDetail>
  13:     {
  14:         //省略成员
  15:     }
  16: }

注:部分内容节选自《WCF技术剖析(卷1)》第五章:序列化与数据契约(Serialization and Data Contract)

WCF技术剖析系列:

WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构
WCF技术剖析之二:再谈IIS与ASP.NET管道
WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿
WCF技术剖析之四:基于IIS的WCF服务寄宿(Hosting)实现揭秘
WCF技术剖析之五:利用ASP.NET兼容模式创建支持会话(Session)的WCF服务
WCF技术剖析之六:为什么在基于ASP.NET应用寄宿(Hosting)下配置的BaseAddress无效
WCF技术剖析之七:如何实现WCF与EnterLib PIAB、Unity之间的集成
WCF技术剖析之八:ClientBase<T>中对ChannelFactory<T>的缓存机制
WCF技术剖析之九:服务代理不能得到及时关闭会有什么后果?
WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理

WCF技术剖析之十一:异步操作在WCF中的应用(上篇)
WCF技术剖析之十一:异步操作在WCF中的应用(下篇)
WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)
WCF技术剖析之十三:序列化过程中的已知类型(Known Type)
WCF技术剖析之十四:泛型数据契约和集合数据契约(上篇)
WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇)
WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用
WCF技术剖析之十六:数据契约的等效性和版本控制



作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
Oracle 关系型数据库 API
C# LIS检验系统源码,接口技术:RESTful API + Http+WCF
LIS检验系统一种专门用于医院化验室的计算机系统,它致力于提高医院化验室的工作效率和检测准确率。LIS系统由多个子系统组成,包括样本管理系统、质控系统、检验结果管理系统、报告管理系统等。体系结构:Client/Server架构 SaaS模式 客户端:WPF+Windows Forms 服务端:C# +.Net 数据库:Oracle 接口技术:RESTful API + Http+WCF
112 2
WCF基础教程(四)——数据契约实现传送自定义数据类型
WCF基础教程(四)——数据契约实现传送自定义数据类型
119 0
|
C#
C#面向服务编程技术WCF从入门到实战演练
一、WCF课程介绍 1.1、Web Service会被WCF取代吗? 对于这个问题阿笨的回答是:两者在功能特性上却是有新旧之分,但是对于特定的系统,适合自己的就是最好的。不能哪一个技术框架和行业标准作比较,任何对于二者的比较都是错误的,因为两者根不不在同一个范畴里。
1397 0
|
安全 C#
WCF技术我们应该如何以正确的方式去学习掌握
一、WCF技术我该如何学习?       阿笨的回答是:作为初学者的我们,那么请跟着阿笨一起玩WCF吧,阿笨将带领大家如何以正确的姿势去掌握WCF技术。由于WCF技术知识点太多了,就纯基础概念性知识都可以单独出一本书来讲解,本次分享课程《C#面向服务编程技术WCF从入门到实战演练》开课之前,阿笨还是希望从没了解过WCF技术的童鞋们提前先了解一下WCF技术,至少要明白WCF技术的ABC三要素分别指的是什么。
1195 0
|
网络架构
WCF入门(二)——终结点,契约(2)
Contract 契约,用于提供消息的标准,消息交换的规则。它分四类: ·服务契约 定义操作 ·数据契约 定义数据 ·异常契约 定义异常 ·消息契约 定义消息格式 (一)服务契约 服务契约,可以用接口定义,也可以直接在类上定义。
671 0
|
网络架构
WCF入门(二)——终结点,契约(2)
Contract 契约,用于提供消息的标准,消息交换的规则。它分四类: ·服务契约 定义操作 ·数据契约 定义数据 ·异常契约 定义异常 ·消息契约 定义消息格式 (一)服务契约 服务契约,可以用接口定义,也可以直接在类上定义。
599 0
|
安全 网络架构
消息(7)——WCF编程模型中控制消息(1)绑定,契约
WCF服务要通过终结点来进行通信,终结点三大构成元素:ABC,其中的B,binding是重中之重,它解决了在消息交换过程中的编码,传输协议,安全等问题。 绑定是分层的,一个绑定对象对应一组有序的绑定元素的集合。
850 0