博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WCF技术剖析之十三:序列化过程中的已知类型(Known Type)
阅读量:6215 次
发布时间:2019-06-21

本文共 7900 字,大约阅读时间需要 26 分钟。

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>方法的定义,请参考本系列的上篇文章:《》。

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
(order, @"E:\order.xml");
11: //或者
12: Serialize
(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
knownTypes);
4: public DataContractSerializer(Type type, string rootName, string rootNamespace, IEnumerable
knownTypes);
5: public DataContractSerializer(Type type, XmlDictionaryString rootName, XmlDictionaryString rootNamespace, IEnumerable
knownTypes);
6: public DataContractSerializer(Type type, IEnumerable
knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate);
7: public DataContractSerializer(Type type, string rootName, string rootNamespace, IEnumerable
knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate);
8: public DataContractSerializer(Type type, XmlDictionaryString rootName, XmlDictionaryString rootNamespace, IEnumerable
knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate);
9:
10: public ReadOnlyCollection
KnownTypes { get; }
11: }

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

1: public static void Serialize
(T instance, string fileName, IList
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
(order, @"E:\order.interface.xml", new List
{
typeof(Order)});
11: Serialize
(order, @"E:\order.class.xml", new List
{ typeof(Order) });
1: 
2: 
NCS
3: 
2008-12-04T00:00:00+08:00
4: 
04c07e41-6302-48d1-ac06-87ebbff2b75f
5: 
#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province
6: 
8888.88
7: 
1: 
2: 
NCS
3: 
2008-12-04T00:00:00+08:00
4: 
04c07e41-6302-48d1-ac06-87ebbff2b75f
5: 
#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province
6: 
8888.88
7: 

实际上,在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: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
作者:蒋金楠
微信公众账号:大内老A
微博:
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号
蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
你可能感兴趣的文章
Excel利用超链接打开指定文件夹
查看>>
Elasticsearch走上专案专有化 AWS:将另开辟开放版
查看>>
J盘无法访问RAW,里面的数据怎样找到
查看>>
硬盘的读写原理
查看>>
photoshop技术
查看>>
python编写员工信息表
查看>>
2018-3-30 Linux学习笔记
查看>>
VM系统安装
查看>>
Squid的ACL访问控制及反向代理
查看>>
Linux配置文件路径大全
查看>>
配置静态路由
查看>>
选择如何启动B2G
查看>>
全球积分宝:揭秘币圈的你是怎么亏钱的!
查看>>
html+css
查看>>
配置AWstats日志分析软件
查看>>
面试题:如何设计一个高并发系统?
查看>>
通过init_connect + binlog 实现MySQL审计功能
查看>>
DenyHosts使用
查看>>
年终总结PPT,这套就够了
查看>>
点击a标签页面加载效果-百度nprogress
查看>>