C# 序列化过程中的已知类型(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>
C# 序列化过程中的已知类型(Known Type)的更多相关文章
- WCF技术剖析之十三:序列化过程中的已知类型(Known Type)
原文:WCF技术剖析之十三:序列化过程中的已知类型(Known Type) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话) ...
- wcf已知类型 known type
.服务契约的定义 /* Copyright (c) 2014 HaiHui Software Co., Ltd. All rights reserved * * Create by huanglc@h ...
- WCF 已知类型和泛型解析程序 KnownType
数据协定继承 已知类型和泛型解析程序 Juval Lowy 下载代码示例 自首次发布以来,Windows Communication Foundation (WCF) 开发人员便必须处理数据协定继承方 ...
- 项目文件中的已知 NuGet 属性(使用这些属性,创建 NuGet 包就可以不需要 nuspec 文件啦)
知道了 csproj 文件中的一些常用 NuGet 属性,创建 NuGet 包时就可以充分发挥新 Sdk 自动生成 NuGet 包的优势,不需要 nuspec 文件啦.(毕竟 nuspec 文件没有 ...
- WCF数据契约代理和已知类型的使用
using Bll; using System; using System.CodeDom; using System.Collections.Generic; using System.Collec ...
- WCF 之 已知类型(KnownType)
已知类型(Known types)允许在服务契约中使用多态的行为,在服务操作中暴露基本类型.将已知类型(known types)相关到基本类型(基类类型)自身;特定操作;整个服务契约采用属性声明或者配 ...
- java基础 File与递归练习 使用文件过滤器筛选将指定文件夹下的小于200K的小文件获取并打印按层次打印(包括所有子文件夹的文件) 多层文件夹情况统计文件和文件夹的数量 统计已知类型的数量 未知类型的数量
package com.swift.kuozhan; import java.io.File; import java.io.FileFilter; /*使用文件过滤器筛选将指定文件夹下的小于200K ...
- 在Excel中,已知身份证号码,如何批量计算其实际年龄?
昨天,上司问我:..,你会在Excel中计算年龄吗?当时,一下促住了.说真的,还真不会.今天研究了一下,写下来,方便日后查看. 首先,得有一张已知姓名和相应身份证号的原表吧. 在这张表上再加上三列:出 ...
- WCF中数据契约之已知类型的几种公开方式
WCF中传输的数据不想传统的面向对象编程,它只传递了一些对象的属性,但是自身并不知道自己属于什么对象,所以,他没有子类和父类的概念,因而也就没有Is-a的关系,所以在WCF中,如果想维持这种继承关系, ...
随机推荐
- 目测ZIP的压缩率
对word文件,能压到25% 对PDF文件,却只有90% 对压缩文件本身再压缩,几乎没有效果. 考虑到用户一般情况下只有正常的文档,取中位值66%是比较正常的情况,特别是在预估原文件大小的时候.
- ECMAScript 5/6/7兼容性速查表
http://kangax.github.io/compat-table/es5/ 秒查ECMAScript在各大浏览器的兼容性,点击右上角按钮可以“在5/6/7/非标”之间切换.做JavaScrip ...
- WCF 基于Cookie的登录验证回传问题的解决
参考资料: http://www.cnblogs.com/czcz1024/p/3333138.html http://megakemp.com/2009/02/06/managing-shared- ...
- jtree(选择框)
jtree一般的用法是: 1. 展示电脑中文件的层次结构,如图所示. 具体的代码: package jtree; import java.io.File; import javax.swing.JTr ...
- C#的隐式和显示类型转换
原文地址:http://blog.csdn.net/yysyangyangyangshan/article/details/7494577 关于隐式转换和显示转换,每种语言都有的,C#中当然也不例外. ...
- ubuntu系统修改终端提示符及设置颜色高亮
Linux终端大家想必都清楚吧,最近在使用的时候发现在进入到某个文件夹目录比较深的层次后,终端提示的绝对路径很长,这样给人的感觉很不习惯,在这里给大家介绍下如何修改终端的提示,顺便介绍下提示符的颜色: ...
- centos Minicom通信终端
minicom是linux下的串口通信软件,他使用完全使用键盘操作.它虽然没有windows下的超级终端好用,但是它也是一种串口通信的方法.一.minicom安装在超级终端中输入:sally@sall ...
- scp 对拷文件夹 和 文件夹下的所有文件 对拷文件并重命名
对拷文件夹 (包括文件夹本身) scp -r /home/wwwroot/www/charts/util root@192.168.1.65:/home/wwwroot/limesurvey_ba ...
- Hadoop,HBase集群环境搭建的问题集锦(四)
21.Schema.xml和solrconfig.xml配置文件里參数说明: 參考资料:http://www.hipony.com/post-610.html 22.执行时报错: 23., /comm ...
- 基于Cordova5.0开发自己定义插件(android)
1.开发插件java部分 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvenhmMjE2MjE2/font/5a6L5L2T/fontsize/400/fi ...