原文:WCF技术剖析之十三:序列化过程中的已知类型(Known Type)

[爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道《天天山海经》为此录制的节目视频(苏州话)]]DataContractSerializer承载着所有数据契约对象的序列化和反序列化操作。在上面一篇文章(《数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)》)中,我们谈到DataContractSerializer基本的序列化规则;如何控制DataContractSerializer序列化或者反序列化对象的数量;以及如何在序列化后的XML中保存被序列化对象的对象引用结构。在这篇文章中,我们会详细讨论WCF序列化中一个重要的话题:已知类型(Known Type)。

WCF下的序列化与反序列化解决的是数据在两种状态之间的相互转化:托管类型对象和XML。由于类型定义了对象的数据结构,所以无论对于序列化还是反序列化,都必须事先确定对象的类型。如果被序列化对象或者被反序列化生成的对象包含不可知的类型,序列化或者反序列化将会失败。为了确保DataContractSerializer的正常序列化和反序列化,我们需要将“未知”类型加入DataContractSerializer“已知”类型列表中。

一、未知类型导致序列化失败

.NET的类型可以分为两种:声明类型和真实类型。我们提倡面向接口的编程,对象的真实类型往往需要在运行时才能确定,在编程的时候往往只需要指明类型的声明类型,比如类型实现的接口或者抽象类。当我们使用基于接口或者抽象类创建的DataContractSerializer去序列化一个实现了该接口或者继承该抽象类的实例的时候,往往会因为对对象的真实类型无法识别造成不能正常地序列化。比如下面的代码中,我们定义了3个类型,一个接口、一个抽象类和一个具体类。

 1: namespace Artech.DataContractSerializerDemos

 2: {

 3: public interface IOrder

 4: {

 5: Guid ID

 6: { get; set; }

 7:  

 8: DateTime Date

 9: { get; set; }

 10:  

 11: string Customer

 12: { get; set; }

 13:  

 14: string ShipAddress

 15: { get; set; }

 16: }

 17:  

 18: [DataContract]

 19: public abstract class OrderBase : IOrder

 20: {

 21: [DataMember]

 22: public Guid ID

 23: { get; set; }

 24:  

 25: [DataMember]

 26: public DateTime Date

 27: { get; set; }

 28:  

 29: [DataMember]

 30: public string Customer

 31: { get; set; }

 32:  

 33: [DataMember]

 34: public string ShipAddress

 35: { get; set; }

 36: }

 37:  

 38: [DataContract]

 39: public class Order : OrderBase

 40: {

 41: [DataMember]

 42: public double TotalPrice

 43: { get; set; }

 44: }

 45: }

当我们通过下面的方式去序列化一个Order对象(注意泛型类型为IOrder或者OrderBase),将会抛出如图1所示SerializationException异常,提示Order类型无法识别。

注:Serialize<T>方法的定义,请参考本系列的上篇文章:《WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)》。

 1: Order order = new Order()

 2: {

 3: ID = Guid.NewGuid(),

 4: Customer = "NCS",

 5: Date = DateTime.Today,

 6: ShipAddress = "#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province",

 7: TotalPrice = 8888.88

 8: };

 9:  

 10: Serialize<IOrder>(order, @"E:\order.xml");

 11: //或者

 12: Serialize<OrderBase>(order, @"E:\order.xml");

图1 “未知”类型导致的序列化异常

二、DataContractSerializer的已知类型集合

解决上面这个问题的唯一途径就是让DataContractSerializer能够识别Order类型,成为DataContractSerializer的已知类型(Known Type)。DataContractSerializer内部具有一个已知类型的列表,我们只需要将Order的类型添加到这个列表中,就能从根本上解决这个问题。通过下面6个重载构造函数中的任意一个,均可以通过knownTypes参数指定DataContractSerializer的已知类型集合,该集合最终反映在DataContractSerializer的制度属性KnownTypes上。

 1: public sealed class DataContractSerializer : XmlObjectSerializer

 2: {

 3: public DataContractSerializer(Type type, IEnumerable<Type> knownTypes);

 4: public DataContractSerializer(Type type, string rootName, string rootNamespace, IEnumerable<Type> knownTypes);

 5: public DataContractSerializer(Type type, XmlDictionaryString rootName, XmlDictionaryString rootNamespace, IEnumerable<Type> knownTypes);

 6: public DataContractSerializer(Type type, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate);

 7: public DataContractSerializer(Type type, string rootName, string rootNamespace, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate);

 8: public DataContractSerializer(Type type, XmlDictionaryString rootName, XmlDictionaryString rootNamespace, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate);

 9: 

 10: public ReadOnlyCollection<Type> KnownTypes { get; }

 11: }

为了方便后面的演示,我们对我们使用的泛型服务方法Serialize<T>为已知类型作相应的修正,通过第3个参数指定DataContractSerializer的已知类型列表。

 1: public static void Serialize<T>(T instance, string fileName, IList<Type> konwnTypes)

 2: {

 3: DataContractSerializer serializer = new DataContractSerializer(typeof(T), konwnTypes, int.MaxValue, false, false, null);

 4: using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8))

 5: {

 6: serializer.WriteObject(writer, instance);

 7: }

 8: Process.Start(fileName);

 9: }

三、基于接口的序列化

DataContractSerializer的创建必须基于某个确定的类型,这里的类型既可以是接口,也可以是抽象类或具体类。不过基于接口的DataContractSerializer与基于抽象数据契约类型的DataContractSerializer,在进行序列化时表现出来的行为是不相同的。

在下面的代码中,在调用Serialize<T>的时候,将泛型类型分别设定为接口IOrder和抽象类OrderBase。虽然是对同一个Order对象进行序列化,但是序列化生成的XML却各有不同。文件order.interface.xml的根节点为<z:anyType>,这是因为DataContractAttribute不能应用于接口上面,所以接口不具有数据契约的概念。<z:anyType>表明能够匹配任意类型,相当于类型object。

 1: Order order = new Order()

 2: {

 3: ID = Guid.NewGuid(),

 4: Customer = "NCS",

 5: Date = DateTime.Today,

 6: ShipAddress = "#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province",

 7: TotalPrice = 8888.88

 8: };

 9:  

 10: Serialize<IOrder>(order, @"E:\order.interface.xml", new List<Type>{typeof(Order)});

 11: Serialize<OrderBase>(order, @"E:\order.class.xml", new List<Type> { typeof(Order) });

 1: <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/">

 2: <d1p1:Customer>NCS</d1p1:Customer>

 3: <d1p1:Date>2008-12-04T00:00:00+08:00</d1p1:Date>

 4: <d1p1:ID>04c07e41-6302-48d1-ac06-87ebbff2b75f</d1p1:ID>

 5: <d1p1:ShipAddress>#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</d1p1:ShipAddress>

 6: <d1p1:TotalPrice>8888.88</d1p1:TotalPrice>

 7: </z:anyType>

 1: <OrderBase xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:type="Order" xmlns="http://schemas.datacontract.org/2004/07/Artech.DataContractSerializerDemos">

 2: <Customer>NCS</Customer>

 3: <Date>2008-12-04T00:00:00+08:00</Date>

 4: <ID>04c07e41-6302-48d1-ac06-87ebbff2b75f</ID>

 5: <ShipAddress>#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</ShipAddress>

 6: <TotalPrice>8888.88</TotalPrice>

 7: </OrderBase>

实际上,在WCF应用中,如果服务契约的操作的参数定义为接口,在发布出来的元数据中,接口类型就相当于object,并且当客户端通过添加服务引用生成客户端服务契约的时候,相应的参数类型就是object类型。比如对于下面的服务契约的定义,当客户端导出后将变成后面的样式。

 1: [ServiceContract(Namespace="http://www.artech.com/")]

 2: public interface IOrderManager

 3: {

 4: [OperationContract]

 5: void ProcessOrder(IOrder order);

 6: }

 1: [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]

 2: [System.ServiceModel.ServiceContractAttribute(ConfigurationName = "ServiceReferences.IOrderManager")]

 3: public interface IOrderManager

 4: {

 5:  

 6: [System.ServiceModel.OperationContractAttribute(Action = "http://www.artech.com/IOrderManager/ProcessOrder", ReplyAction = "http://www.artech.com/IOrderManager/ProcessOrderResponse")]

 7: void ProcessOrder(object order);

 8: }

四、 KnownTypeAttribute与ServiceKnownTypeAttribute

对于已知类型,可以通过两个特殊的自定义特性进行设置:KnownTypeAttribute和ServiceKnownTypeAttribute。KnownTypeAttribute应用于数据契约中,用于设置继承与该数据契约类型的子数据契约类型,或者引用的其他潜在的类型。ServiceKnownTypeAttribute既可以应用于服务契约的接口和方法上,也可以应用在服务实现的类和方法上。应用的目标元素决定了定义的已知类型的作用范围。下面的代码中,在基类OrderBase指定了子类的类型Order。

 1: [DataContract]

 2: [KnownType(typeof(Order))]

 3: public abstract class OrderBase : IOrder

 4: {

 5: //省略成员

 6: }

而ServiceKnownTypeAttribute特性,仅可以使用在服务契约类型上,也可以应用在服务契约的操作方法上。如果应用在服务契约类型上,已知类型在所有实现了该契约的服务操作中有效,如果应用于服务契约的操作方法上,则定义的已知类型在所有实现了该契约的服务对应的操作中有效。

 1: [ServiceContract]

 2: [ServiceKnownType(typeof(Order))]

 3: public interface IOrderManager

 4: {

 5: [OperationContract]

 6: void ProcessOrder(OrderBase order);

 7: }

 1: [ServiceContract]

 2: public interface IOrderManager

 3: {

 4: [OperationContract]

 5: [ServiceKnownType(typeof(Order))]

 6: void ProcessOrder(OrderBase order);

 7: }

ServiceKnownTypeAttribute也可以应用于具体的服务类型和方法上面。对于前者,通过ServiceKnownTypeAttribute定义的已知类型在整个服务的所有方法中有效,而对于后者,则已知类型仅限于当前方法。

 1: [ServiceKnownType(typeof(Order))]

 2: public class OrderManagerService : IOrderManager

 3: { 

 4: public void ProcessOrder(OrderBase order)

 5: {

 6: //省略成员

 7: }

 8: }

 1: public class OrderManagerService : IOrderManager

 2: {

 3: [ServiceKnownType(typeof(Order))]

 4: public void ProcessOrder(OrderBase order)

 5: {

 6: //省略成员

 7: }

 8: }

除了通过自定义特性的方式设置已知类型外,已知类型还可以通过配置的方式进行指定。已知类型定义在<system.runtime.serialization>配置节中,采用如下的定义方式。这和我们在上面通过KnownTypeAttribute指定Order类型是完全等效的。

 1: <?xml version="1.0" encoding="utf-8" ?>

 2: <configuration>

 3: <system.runtime.serialization>

 4: <dataContractSerializer>

 5: <declaredTypes>

 6: <add type="Artech.DataContractSerializerDemos.OrderBase,Artech.DataContractSerializerDemos.KnownTypes">

 7: <knownType type="Artech.DataContractSerializerDemos.Order,Artech.DataContractSerializerDemos.KnownTypes"/>

 8: </add>

 9: </declaredTypes>

 10: </dataContractSerializer>

 11: </system.runtime.serialization>

 12: </configuration>

作者:Artech
出处:http://artech.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

WCF技术剖析之十三:序列化过程中的已知类型(Known Type)的更多相关文章

  1. C# 序列化过程中的已知类型(Known Type)

    WCF下的序列化与反序列化解决的是数据在两种状态之间的相互转化:托管类型对象和XML.由于类型定义了对象的数据结构,所以无论对于序列化还是反序列化,都必须事先确定对象的类型.如果被序列化对象或者被反序 ...

  2. WCF技术剖析之八:ClientBase<T>中对ChannelFactory<T>的缓存机制

    原文:WCF技术剖析之八:ClientBase<T>中对ChannelFactory<T>的缓存机制 和传统的分布式远程调用一样,WCF的服务调用借助于服务代理(Service ...

  3. wcf已知类型 known type

    .服务契约的定义 /* Copyright (c) 2014 HaiHui Software Co., Ltd. All rights reserved * * Create by huanglc@h ...

  4. WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用

    原文:WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用 [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经> ...

  5. 《WCF技术剖析》博文系列汇总[持续更新中]

    原文:<WCF技术剖析>博文系列汇总[持续更新中] 近半年以来,一直忙于我的第一本WCF专著<WCF技术剖析(卷1)>的写作,一直无暇管理自己的Blog.在<WCF技术剖 ...

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

    原文:WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话)]]在.NE ...

  7. WCF技术剖析之七:如何实现WCF与EnterLib PIAB、Unity之间的集成

    原文:WCF技术剖析之七:如何实现WCF与EnterLib PIAB.Unity之间的集成 在这之前,我写过深入介绍MS EnterLib PIAB的文章(参阅<MS Enterprise Li ...

  8. WCF 已知类型和泛型解析程序 KnownType

    数据协定继承 已知类型和泛型解析程序 Juval Lowy 下载代码示例 自首次发布以来,Windows Communication Foundation (WCF) 开发人员便必须处理数据协定继承方 ...

  9. WCF技术剖析之二十三:服务实例(Service Instance)生命周期如何控制[下篇]

    原文:WCF技术剖析之二十三:服务实例(Service Instance)生命周期如何控制[下篇] 在[第2篇]中,我们深入剖析了单调(PerCall)模式下WCF对服务实例生命周期的控制,现在我们来 ...

随机推荐

  1. WebRTC–getUserMedia & Canvas

    下面是一个使用getUserMedia接口和Canvas的drawImage方法实现的截图功能(截取视频中的一帧). 基本思路是这样子的: getUserMedia获取一个MediaStream, s ...

  2. 挺有意思的HBase日志+Splunk

    如题,转载自: http://hi.baidu.com/harry_lime/item/10cf2c174853c7ea39cb3042 如何模拟拔盘操作 Linux has a nifty way ...

  3. VS2010/MFC对话框二:为对话框添加控件)

    为对话框添加控件 创建对话框资源需要创建对话框模板.修改对话框属性.为对话框添加各种控件等步骤,前面一讲中已经讲了创建对话框模板和修改对话框属性,本节继续讲如何为对话框添加控件. 上一讲中创建了一个名 ...

  4. APUE学习笔记——10.15 sigsetjmp和siglongjmp

    转载自:sigsetjmp使用方法 如侵犯您的权益,请联系:windeal12@qq.com sigsetjmp使用方法 分类: c/c++ linux2012-02-03 12:33 1252人阅读 ...

  5. Codeforces Round #262 (Div. 2) 460C. Present(二分)

    题目链接:http://codeforces.com/problemset/problem/460/C C. Present time limit per test 2 seconds memory ...

  6. 【centos6 , 7】 网络原理、网络配置

    第一部分:网络原理: 一.网络编址 (主要使用IP编址) 1.ip编址是一个双层编址方案,一个ip地址标识一个主机(或一个网卡接口) 2.现在应用最为广泛的是IPv4编址,已经开始逐渐向IPv6编址转 ...

  7. 【STL__set_的应用】

    1.关于set C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器, 更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结 ...

  8. POJ 2187 旋转卡壳 + 水平序 Graham 扫描算法 + 运算符重载

    水平序 Graham 扫描算法: 计算二维凸包的时候可以用到,Graham 扫描算法有水平序和极角序两种. 极角序算法能一次确定整个凸包, 但是计算极角需要用到三角函数,速度较慢,精度较差,特殊情况较 ...

  9. POJ 3047 Fibonacci

    DEBUG很辛苦,且行, 且珍惜 原代码: ans[0][0] = (ans[0][0] * a[flag][0][0] + ans[0][1] * a[flag][1][0]) % 10000; a ...

  10. VirtualBox虚拟机安装RedHat7.3编译Linux0.01内核

    引子 由于需要编译linux0.01内核,而目前的linux版本太高需要降低gcc版本等等,需要做不少调整非常不方便. 所以,直接安装RedHat7.3,这样就好编译linux0.01的内核了. 但是 ...