[爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道《天天山海经》为此录制的节目视频(苏州话)]]大部分的系统都是以数据为中心的(Data Central),功能的实现表现在对相关数据的正确处理。而数据本身,是有效信息的载体,在不同的环境具有不同的表示。一个分布式的互联系统关注于数据的交换,而数据正常交换的根本前提是参与数据交换的双方对于数据结构的一致性理解。这就为数据的表现提出了要求,为了保证处于不同平台、不同厂商的应用能够正常地进行数据交换,交换的数据必须采用一种大家都能够理解的展现方式。在这方面,XML无疑是最好的选择。所以WCF下的序列化(Serialization)解决的就是如何将数据从对象的表现形式转变成XML表现形式,以确保数据的正常交换。从本章起,我将讲述WCF序列化的本质,首先从从数据契约谈起。
一、数据契约
一个正常的服务调用要求客户端和服务端对服务操作有一致的理解,WCF通过服务契约对服务操作进行抽象,以一种与平台无关的,能够被不同的厂商理解的方式对服务进行描述。同理,客户端和服务端进行有效的数据交换,同样要求交换双方对交换数据的结构达成共识,WCF通过数据契约来对交换的数据进行描述。与数据契约的定义相匹配,WCF采用新的序列化器——数据契约序列化器(DataContractSerializer)进行基于数据契约的序列化于反序列化操作。
同服务契约类似,WCF采用了基于特性(Attribute)的数据契约定义方式。基于数据契约的自定义特性主要包含以下两个:DataContractAttribute和DataMemberAttribute,接下来我们将讨论这两个重要的自定义特性。
DataContractAttribute和DataMemberAttribute
WCF通过应用DataContractAttribute特性将其目标类型定义成一个数据契约,下面是DataContractAttribute的定义。从AttributeUsage的定义来看,DataContractAttribute只能用于枚举、类和结构体,而不能用于接口;DataContractAttribute是不可以被继承的,也就是说当一个类型继承了一个应用了DataContractAttribute特性类型,自身也只有显式地应用DataContractAttribute特性才能成为数据契约;一个类型上只能应用唯一一个DataContractAttribute特性。
1: [AttributeUsage(AttributeTargets.Enum | AttributeTargets.Struct | AttributeTargets.Class, Inherited = false, AllowMultiple = false)]<!--CRLF-->
2: public sealed class DataContractAttribute : Attribute<!--CRLF-->
3: {
<!--CRLF-->
4: public bool IsReference { get; set; }<!--CRLF-->
5: public string Name { get; set; }<!--CRLF-->
6: public string Namespace { get; set; }<!--CRLF-->
7: }
<!--CRLF-->
DataContractAttribute仅仅包含3个属性成员。其中Name和Namespace表示数据契约的名称和命名空间;IsReference表示在进行序列化的时候是否保持对象现有的引用结构。比如说,一个对象的两个属性同时引用一个对象,那么有两个序列化方式,一种是在序列化后的XML仍然保留这种引用结构,另一种是将两个属性的值序列化成两份独立的具有相同内容的XML。
对于服务契约来说,我们在一个接口或者类上面应用的ServiceContractAttribute将其定义成服务契约后,并不意味着该接口或者类中的每一个方法成员都是服务操作,而是通过OperationContractAttribute显式地将相应的方法定义成服务操作。与之类似,数据契约也采用这种显式声明的机制。对于应用了DataContractAttribute特性的类型,只有应用了DataMemberAttribute特性的字段或者属性成员才能成为数据契约的数据成员。DataMemberAttribute特性的定义如下所示。
1: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]<!--CRLF-->
2: public sealed class DataMemberAttribute : Attribute<!--CRLF-->
3: {
<!--CRLF-->
4: public DataMemberAttribute();<!--CRLF-->
5:
<!--CRLF-->
6: public bool EmitDefaultValue { get; set; }<!--CRLF-->
7: public bool IsRequired { get; set; }<!--CRLF-->
8: public string Name { get; set; }<!--CRLF-->
9: public int Order { get; set; }<!--CRLF-->
10: }
<!--CRLF-->
下面的列表列出了DataMemberAttribute的4个属性所表述的含义。
- Name:数据成员的名称,默认为字段或者属性的名称;
- Order:相应的数据成员在最终序列化后的XML出现的位置,Order值越小越靠前,默认值为-1;
- IsRequired:表明属性成员是否是必须的成员,默认值为false,表明该成员是可以缺省的;
- EmitDefaultValue:表明在数据成员的值等于默认值的情况下,是否还需要将其序列化到最终的XML中,默认值为true,表示默认值会参与序列化。
注: 数据契约和数据成员只和是否应用了DataContractAttribute和DataMemberAttribute有关,与类型和成员的存取限制修饰符(public,internal、protected,private等)无关。也就是说,应用了DataMemberAttribute的私有字段或属性成员也是数据契约的数据成员。
二、数据契约序列化器(DataContractSerializer)
在WCF中,数据契约的定义是为序列化和反序列化服务的。WCF采用数据契约序列化器(DataContractSerializer)作为默认的序列化器。接下来我们着重谈谈DataContractSerializer和基于DataContractSerializer采用的序列化规则。先来看看DataContractSerializer的定义。
1: public sealed class DataContractSerializer : XmlObjectSerializer<!--CRLF-->
2: {
<!--CRLF-->
3: //其他成员<!--CRLF-->
4: public DataContractSerializer(Type type);<!--CRLF-->
5: //其他构造函数<!--CRLF-->
6:
<!--CRLF-->
7: public override object ReadObject(XmlReader reader);<!--CRLF-->
8: public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName);<!--CRLF-->
9: public override object ReadObject(XmlReader reader, bool verifyObjectName);<!--CRLF-->
10: public override void WriteObject(XmlWriter writer, object graph);<!--CRLF-->
11:
<!--CRLF-->
12: public IDataContractSurrogate DataContractSurrogate { get; }<!--CRLF-->
13: public bool IgnoreExtensionDataObject { get; }<!--CRLF-->
14: public ReadOnlyCollection<Type> KnownTypes { get; }<!--CRLF-->
15: public int MaxItemsInObjectGraph { get; }<!--CRLF-->
16: public bool PreserveObjectReferences { get; }<!--CRLF-->
17: }
<!--CRLF-->
DataContractSerializer定义了一系列的重载的构造函数,我们可以调用它们构建相应的DataContractSerializer对象,通过制定相应的参数控制系列化器的序列化和反序列化行为。在后续的介绍中我们会通过这些相应的构造函数创建DataContractSerializer对象,在这里就不一一介绍了。DataContractSerializer主要通过两个方法进行序列化和反序列化:WirteObject和ReadObject。这里我们需要着重介绍一下DataContractSerializer的5个重要的属性成员。
- DataContractSurrogate:这是一个实现了IDataContractSurrogate接口的数据契约代理类的对象。契约代理会参与到DataContractSerializer的序列化、反序列化以及契约的导入和导出的过程中,实现对象和类型的替换;
- IgnoreExtensionDataObject:扩展数据对象(ExtensionDataObject)旨在解决双方数据契约不一致的情况下,在数据传送-回传(Round Trip)过程中造成的数据丢失;
- KnownTypes:由于序列化和反序列化依赖于定义在类型的元数据信息,所以在进行序列化或者反序列化之前,需要确定被序列化对象,或者反序列化生成对象的所有相关的真实类型。为了确保序列化或反序列化的成功,须要相关的类型添加到KnownTypes类型集合中;
- MaxItemsInObjectGraph:为了避免黑客生成较大数据,频繁地访问服务造成服务器不堪重负(我们一般把这种黑客行为称为拒绝服务DoS-Denial of Service),可以通过MaxItemsInObjectGraph属性设置进行序列化和反序列化允许的最大对象数。MaxItemsInObjectGraph的默认值为65536;
- PreserveObjectReferences:这个属性与DataContractAttribute的IsReference属性的含义一样,表示的是如果数据对象的多个属性或者字段引用相同的对象,在序列化的时候是否需要在XML中保持一样的引用结构。
三、基于DataContractSerializer的序列化规则
与在第一节介绍XmlSerializer的序列化规则一样,现在我们通过一个具体的例子来介绍DataContractSerializer是如何进行序列化的,以及采用怎样的序列化规则。我们照例定义一个泛型的辅助方法进行专门的序列化工作,最终生成的XML保存到一个XML文件中。
1: public static void Serialize<T>(T instance, string fileName)<!--CRLF-->
2: {
<!--CRLF-->
3: DataContractSerializer serializer = new DataContractSerializer(typeof(T));<!--CRLF-->
4: using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8))<!--CRLF-->
5: {
<!--CRLF-->
6: serializer.WriteObject(writer, instance);
<!--CRLF-->
7: }
<!--CRLF-->
8: Process.Start(fileName);
<!--CRLF-->
9: }
<!--CRLF-->
我们需要对一个Order对象进行序列化,Order类型的定义如下。实际上我们定义了两个数据契约:OrderBase和Order,Order继承于OrderBase。
1: namespace Artech.DataContractSerializerDemos<!--CRLF-->
2: {
<!--CRLF-->
3: [DataContract]
<!--CRLF-->
4: public class OrderBase<!--CRLF-->
5: {
<!--CRLF-->
6: [DataMember]
<!--CRLF-->
7: public Guid ID<!--CRLF-->
8: { get; set; }
<!--CRLF-->
9:
<!--CRLF-->
10: [DataMember]
<!--CRLF-->
11: public DateTime Date<!--CRLF-->
12: { get; set; }
<!--CRLF-->
13:
<!--CRLF-->
14: [DataMember]
<!--CRLF-->
15: public string Customer<!--CRLF-->
16: { get; set; }
<!--CRLF-->
17:
<!--CRLF-->
18: [DataMember]
<!--CRLF-->
19: public string ShipAddress<!--CRLF-->
20: { get; set; }
<!--CRLF-->
21:
<!--CRLF-->
22: public double TotalPrice<!--CRLF-->
23: { get; set; }
<!--CRLF-->
24: }
<!--CRLF-->
25:
<!--CRLF-->
26: [DataContract]
<!--CRLF-->
27: public class Order : OrderBase<!--CRLF-->
28: {
<!--CRLF-->
29: [DataMember]
<!--CRLF-->
30: public string PaymentType<!--CRLF-->
31: { get; set; }
<!--CRLF-->
32: }
<!--CRLF-->
33: }
<!--CRLF-->
通过下面的代码对创建的Order对象进行序列化,会生成一段XML。
1: Order order = new Order()<!--CRLF-->
2: {
<!--CRLF-->
3: ID = Guid.NewGuid(),
<!--CRLF-->
4: Date = DateTime.Today,
<!--CRLF-->
5: Customer = "NCS",<!--CRLF-->
6: ShipAddress = "#328, Airport Rd, Industrial Park, Suzhou JiangSu Province",<!--CRLF-->
7: TotalPrice = 8888,
<!--CRLF-->
8: PaymentType = "Credit Card"<!--CRLF-->
9: };
<!--CRLF-->
10: Serialize(order, @"E:\order.xml");<!--CRLF-->
1: <Order xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Artech.DataContractSerializerDemos"><!--CRLF-->
2: <Customer>NCS</Customer><!--CRLF-->
3: <Date>2008-12-03T00:00:00+08:00</Date><!--CRLF-->
4: <ID>5fdbee36-e29e-48d2-b45f-6fd4beba54d6</ID><!--CRLF-->
5: <ShipAddress>#328, Airport Rd, Industrial Park, Suzhou JiangSu Province</ShipAddress><!--CRLF-->
6: <PaymentType>Credit Card</PaymentType><!--CRLF-->
7: </Order>