WCF技术剖析之十三:序列化过程中的已知类型(Known Type)
2009-07-12 16:27
666 查看
[爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道《天天山海经》为此录制的节目视频(苏州话)]]DataContractSerializer承载着所有数据契约对象的序列化和反序列化操作。在上面一篇文章(《数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)》)中,我们谈到DataContractSerializer基本的序列化规则;如何控制DataContractSerializer序列化或者反序列化对象的数量;以及如何在序列化后的XML中保存被序列化对象的对象引用结构。在这篇文章中,我们会详细讨论WCF序列化中一个重要的话题:已知类型(Known Type)。
WCF下的序列化与反序列化解决的是数据在两种状态之间的相互转化:托管类型对象和XML。由于类型定义了对象的数据结构,所以无论对于序列化还是反序列化,都必须事先确定对象的类型。如果被序列化对象或者被反序列化生成的对象包含不可知的类型,序列化或者反序列化将会失败。为了确保DataContractSerializer的正常序列化和反序列化,我们需要将“未知”类型加入DataContractSerializer“已知”类型列表中。
一、未知类型导致序列化失败
.NET的类型可以分为两种:声明类型和真实类型。我们提倡面向接口的编程,对象的真实类型往往需要在运行时才能确定,在编程的时候往往只需要指明类型的声明类型,比如类型实现的接口或者抽象类。当我们使用基于接口或者抽象类创建的DataContractSerializer去序列化一个实现了该接口或者继承该抽象类的实例的时候,往往会因为对对象的真实类型无法识别造成不能正常地序列化。比如下面的代码中,我们定义了3个类型,一个接口、一个抽象类和一个具体类。
当我们通过下面的方式去序列化一个Order对象(注意泛型类型为IOrder或者OrderBase),将会抛出如图1所示SerializationException异常,提示Order类型无法识别。
注:Serialize<T>方法的定义,请参考本系列的上篇文章:《WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)》。
图1 “未知”类型导致的序列化异常
二、DataContractSerializer的已知类型集合
解决上面这个问题的唯一途径就是让DataContractSerializer能够识别Order类型,成为DataContractSerializer的已知类型(Known Type)。DataContractSerializer内部具有一个已知类型的列表,我们只需要将Order的类型添加到这个列表中,就能从根本上解决这个问题。通过下面6个重载构造函数中的任意一个,均可以通过knownTypes参数指定DataContractSerializer的已知类型集合,该集合最终反映在DataContractSerializer的制度属性KnownTypes上。
为了方便后面的演示,我们对我们使用的泛型服务方法Serialize<T>为已知类型作相应的修正,通过第3个参数指定DataContractSerializer的已知类型列表。
三、基于接口的序列化
DataContractSerializer的创建必须基于某个确定的类型,这里的类型既可以是接口,也可以是抽象类或具体类。不过基于接口的DataContractSerializer与基于抽象数据契约类型的DataContractSerializer,在进行序列化时表现出来的行为是不相同的。
在下面的代码中,在调用Serialize<T>的时候,将泛型类型分别设定为接口IOrder和抽象类OrderBase。虽然是对同一个Order对象进行序列化,但是序列化生成的XML却各有不同。文件order.interface.xml的根节点为<z:anyType>,这是因为DataContractAttribute不能应用于接口上面,所以接口不具有数据契约的概念。<z:anyType>表明能够匹配任意类型,相当于类型object。
实际上,在WCF应用中,如果服务契约的操作的参数定义为接口,在发布出来的元数据中,接口类型就相当于object,并且当客户端通过添加服务引用生成客户端服务契约的时候,相应的参数类型就是object类型。比如对于下面的服务契约的定义,当客户端导出后将变成后面的样式。
四、 KnownTypeAttribute与ServiceKnownTypeAttribute
对于已知类型,可以通过两个特殊的自定义特性进行设置:KnownTypeAttribute和ServiceKnownTypeAttribute。KnownTypeAttribute应用于数据契约中,用于设置继承与该数据契约类型的子数据契约类型,或者引用的其他潜在的类型。ServiceKnownTypeAttribute既可以应用于服务契约的接口和方法上,也可以应用在服务实现的类和方法上。应用的目标元素决定了定义的已知类型的作用范围。下面的代码中,在基类OrderBase指定了子类的类型Order。
而ServiceKnownTypeAttribute特性,仅可以使用在服务契约类型上,也可以应用在服务契约的操作方法上。如果应用在服务契约类型上,已知类型在所有实现了该契约的服务操作中有效,如果应用于服务契约的操作方法上,则定义的已知类型在所有实现了该契约的服务对应的操作中有效。
ServiceKnownTypeAttribute也可以应用于具体的服务类型和方法上面。对于前者,通过ServiceKnownTypeAttribute定义的已知类型在整个服务的所有方法中有效,而对于后者,则已知类型仅限于当前方法。
除了通过自定义特性的方式设置已知类型外,已知类型还可以通过配置的方式进行指定。已知类型定义在<system.runtime.serialization>配置节中,采用如下的定义方式。这和我们在上面通过KnownTypeAttribute指定Order类型是完全等效的。
WCF下的序列化与反序列化解决的是数据在两种状态之间的相互转化:托管类型对象和XML。由于类型定义了对象的数据结构,所以无论对于序列化还是反序列化,都必须事先确定对象的类型。如果被序列化对象或者被反序列化生成的对象包含不可知的类型,序列化或者反序列化将会失败。为了确保DataContractSerializer的正常序列化和反序列化,我们需要将“未知”类型加入DataContractSerializer“已知”类型列表中。
一、未知类型导致序列化失败
.NET的类型可以分为两种:声明类型和真实类型。我们提倡面向接口的编程,对象的真实类型往往需要在运行时才能确定,在编程的时候往往只需要指明类型的声明类型,比如类型实现的接口或者抽象类。当我们使用基于接口或者抽象类创建的DataContractSerializer去序列化一个实现了该接口或者继承该抽象类的实例的时候,往往会因为对对象的真实类型无法识别造成不能正常地序列化。比如下面的代码中,我们定义了3个类型,一个接口、一个抽象类和一个具体类。
namespace Artech.DataContractSerializerDemos { public interface IOrder { Guid ID { get; set; } DateTime Date { get; set; } string Customer { get; set; } string ShipAddress { get; set; } } [DataContract] public abstract class OrderBase : IOrder { [DataMember] public Guid ID { get; set; } [DataMember] public DateTime Date { get; set; } [DataMember] public string Customer { get; set; } [DataMember] public string ShipAddress { get; set; } } [DataContract] public class Order : OrderBase { [DataMember] public double TotalPrice { get; set; } } }
当我们通过下面的方式去序列化一个Order对象(注意泛型类型为IOrder或者OrderBase),将会抛出如图1所示SerializationException异常,提示Order类型无法识别。
注:Serialize<T>方法的定义,请参考本系列的上篇文章:《WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)》。
Order order = new Order() { ID = Guid.NewGuid(), Customer = "NCS", Date = DateTime.Today, ShipAddress = "#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province", TotalPrice = 8888.88 }; Serialize<IOrder>(order, @"E:\order.xml"); //或者 Serialize<OrderBase>(order, @"E:\order.xml");
图1 “未知”类型导致的序列化异常
二、DataContractSerializer的已知类型集合
解决上面这个问题的唯一途径就是让DataContractSerializer能够识别Order类型,成为DataContractSerializer的已知类型(Known Type)。DataContractSerializer内部具有一个已知类型的列表,我们只需要将Order的类型添加到这个列表中,就能从根本上解决这个问题。通过下面6个重载构造函数中的任意一个,均可以通过knownTypes参数指定DataContractSerializer的已知类型集合,该集合最终反映在DataContractSerializer的制度属性KnownTypes上。
public sealed class DataContractSerializer : XmlObjectSerializer { public DataContractSerializer(Type type, IEnumerable<Type> knownTypes); public DataContractSerializer(Type type, string rootName, string rootNamespace, IEnumerable<Type> knownTypes); public DataContractSerializer(Type type, XmlDictionaryString rootName, XmlDictionaryString rootNamespace, IEnumerable<Type> knownTypes); public DataContractSerializer(Type type, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate); public DataContractSerializer(Type type, string rootName, string rootNamespace, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate); public DataContractSerializer(Type type, XmlDictionaryString rootName, XmlDictionaryString rootNamespace, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate); public ReadOnlyCollection<Type> KnownTypes { get; } }
为了方便后面的演示,我们对我们使用的泛型服务方法Serialize<T>为已知类型作相应的修正,通过第3个参数指定DataContractSerializer的已知类型列表。
public static void Serialize<T>(T instance, string fileName, IList<Type> konwnTypes) { DataContractSerializer serializer = new DataContractSerializer(typeof(T), konwnTypes, int.MaxValue, false, false, null); using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8)) { serializer.WriteObject(writer, instance); } Process.Start(fileName); }
三、基于接口的序列化
DataContractSerializer的创建必须基于某个确定的类型,这里的类型既可以是接口,也可以是抽象类或具体类。不过基于接口的DataContractSerializer与基于抽象数据契约类型的DataContractSerializer,在进行序列化时表现出来的行为是不相同的。
在下面的代码中,在调用Serialize<T>的时候,将泛型类型分别设定为接口IOrder和抽象类OrderBase。虽然是对同一个Order对象进行序列化,但是序列化生成的XML却各有不同。文件order.interface.xml的根节点为<z:anyType>,这是因为DataContractAttribute不能应用于接口上面,所以接口不具有数据契约的概念。<z:anyType>表明能够匹配任意类型,相当于类型object。
Order order = new Order() { ID = Guid.NewGuid(), Customer = "NCS", Date = DateTime.Today, ShipAddress = "#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province", TotalPrice = 8888.88 }; Serialize<IOrder>(order, @"E:\order.interface.xml", new List<Type>{typeof(Order)}); Serialize<OrderBase>(order, @"E:\order.class.xml", new List<Type> { typeof(Order) });
<z:anyType xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:d1p1="http://schemas.datacontract.org/2004/07/Artech.DataContractSerializerDemos" i:type="d1p1:Order" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> <d1p1:Customer>NCS</d1p1:Customer> <d1p1:Date>2008-12-04T00:00:00+08:00</d1p1:Date> <d1p1:ID>04c07e41-6302-48d1-ac06-87ebbff2b75f</d1p1:ID> <d1p1:ShipAddress>#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</d1p1:ShipAddress> <d1p1:TotalPrice>8888.88</d1p1:TotalPrice> </z:anyType>
<OrderBase xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:type="Order" xmlns="http://schemas.datacontract.org/2004/07/Artech.DataContractSerializerDemos"> <Customer>NCS</Customer> <Date>2008-12-04T00:00:00+08:00</Date> <ID>04c07e41-6302-48d1-ac06-87ebbff2b75f</ID> <ShipAddress>#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</ShipAddress> <TotalPrice>8888.88</TotalPrice> </OrderBase>
实际上,在WCF应用中,如果服务契约的操作的参数定义为接口,在发布出来的元数据中,接口类型就相当于object,并且当客户端通过添加服务引用生成客户端服务契约的时候,相应的参数类型就是object类型。比如对于下面的服务契约的定义,当客户端导出后将变成后面的样式。
[ServiceContract(Namespace="http://www.artech.com/")] public interface IOrderManager { [OperationContract] void ProcessOrder(IOrder order); }
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] [System.ServiceModel.ServiceContractAttribute(ConfigurationName = "ServiceReferences.IOrderManager")] public interface IOrderManager { [System.ServiceModel.OperationContractAttribute(Action = "http://www.artech.com/IOrderManager/ProcessOrder", ReplyAction = "http://www.artech.com/IOrderManager/ProcessOrderResponse")] void ProcessOrder(object order); }
四、 KnownTypeAttribute与ServiceKnownTypeAttribute
对于已知类型,可以通过两个特殊的自定义特性进行设置:KnownTypeAttribute和ServiceKnownTypeAttribute。KnownTypeAttribute应用于数据契约中,用于设置继承与该数据契约类型的子数据契约类型,或者引用的其他潜在的类型。ServiceKnownTypeAttribute既可以应用于服务契约的接口和方法上,也可以应用在服务实现的类和方法上。应用的目标元素决定了定义的已知类型的作用范围。下面的代码中,在基类OrderBase指定了子类的类型Order。
[DataContract] [KnownType(typeof(Order))] public abstract class OrderBase : IOrder { //省略成员 }
而ServiceKnownTypeAttribute特性,仅可以使用在服务契约类型上,也可以应用在服务契约的操作方法上。如果应用在服务契约类型上,已知类型在所有实现了该契约的服务操作中有效,如果应用于服务契约的操作方法上,则定义的已知类型在所有实现了该契约的服务对应的操作中有效。
[ServiceContract] [ServiceKnownType(typeof(Order))] public interface IOrderManager { [OperationContract] void ProcessOrder(OrderBase order); }
[ServiceContract] public interface IOrderManager { [OperationContract] [ServiceKnownType(typeof(Order))] void ProcessOrder(OrderBase order); }
ServiceKnownTypeAttribute也可以应用于具体的服务类型和方法上面。对于前者,通过ServiceKnownTypeAttribute定义的已知类型在整个服务的所有方法中有效,而对于后者,则已知类型仅限于当前方法。
[ServiceKnownType(typeof(Order))] public class OrderManagerService : IOrderManager { public void ProcessOrder(OrderBase order) { //省略成员 } }
public class OrderManagerService : IOrderManager { [ServiceKnownType(typeof(Order))] public void ProcessOrder(OrderBase order) { //省略成员 } }
除了通过自定义特性的方式设置已知类型外,已知类型还可以通过配置的方式进行指定。已知类型定义在<system.runtime.serialization>配置节中,采用如下的定义方式。这和我们在上面通过KnownTypeAttribute指定Order类型是完全等效的。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.serialization> <dataContractSerializer> <declaredTypes> <add type="Artech.DataContractSerializerDemos.OrderBase,Artech.DataContractSerializerDemos.KnownTypes"> <knownType type="Artech.DataContractSerializerDemos.Order,Artech.DataContractSerializerDemos.KnownTypes"/> </add> </declaredTypes> </dataContractSerializer> </system.runtime.serialization> </configuration>
相关文章推荐
- WCF技术剖析之十三:序列化过程中的已知类型(Known Type)
- [原创]WCF技术剖析之十三:序列化过程中的已知类型(Known Type)
- WCF技术剖析之十三:序列化过程中的已知类型(Known Type)
- C# 序列化过程中的已知类型(Known Type)
- WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化1
- wcf已知类型 known type
- WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用
- WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化
- [原创] WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化
- [原创]WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用
- wcf 基础教程 之已知类型KnownType 数据契约序列化DataContractSerializer
- WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化
- WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化
- WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用
- CDN高级技术专家周哲:深度剖析短视频分发过程中的用户体验优化技术点
- 深度剖析短视频分发过程中的用户体验优化技术点
- WCF技术剖析之二十: 服务在WCF体系中是如何被描述的?
- WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序)
- 诟病WCF之一,对已知类型的支持比较弱智(WCF面向接口编程)
- WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载]