閱讀835 返回首頁    go 阿裏雲 go 技術社區[雲棲]


WCF技術剖析之十三:序列化過程中的已知類型(Known Type)

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>方法的定義,請參考本係列的上篇文章:《

 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");

clip_image002

圖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="https://www.w3.org/2001/XMLSchema-instance" xmlns:d1p1="https://schemas.datacontract.org/2004/07/Artech.DataContractSerializerDemos" i:type="d1p1:Order" xmlns:z="https://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="https://www.w3.org/2001/XMLSchema-instance" i:type="Order" xmlns="https://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="https://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 = "https://www.artech.com/IOrderManager/ProcessOrder", ReplyAction = "https://www.artech.com/IOrderManager/ProcessOrderResponse")]
 7: void ProcessOrder(object order);
 8: }

對於已知類型,可以通過兩個特殊的自定義特性進行設置: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>

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 13:33:40

  上一篇:go  WCF技術剖析之十二:數據契約(Data Contract)和數據契約序列化器(DataContractSerializer)
  下一篇:go  百度自動駕駛事業部總監孫勇義:Apollo計劃背後的AI技術| 清華x-lab AI研習社