天天看點

[原創]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");      
[原創]WCF技術剖析之十三:序列化過程中的已知類型(Known Type)

圖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>      

注:部分内容節選自《WCF技術剖析(卷1)》第四章:序列化與資料契約(Serialization and Data Contract)

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

WCF技術剖析系列:

WCF技術剖析之一:通過一個ASP.NET程式模拟WCF基礎架構

WCF技術剖析之二:再談IIS與ASP.NET管道

WCF技術剖析之三:如何進行基于非HTTP的IIS服務寄宿

WCF技術剖析之四:基于IIS的WCF服務寄宿(Hosting)實作揭秘

WCF技術剖析之五:利用ASP.NET相容模式建立支援會話(Session)的WCF服務

WCF技術剖析之六:為什麼在基于ASP.NET應用寄宿(Hosting)下配置的BaseAddress無效

WCF技術剖析之七:如何實作WCF與EnterLib PIAB、Unity之間的內建

WCF技術剖析之八:ClientBase<T>中對ChannelFactory<T>的緩存機制

WCF技術剖析之九:服務代理不能得到及時關閉會有什麼後果?

WCF技術剖析之十:調用WCF服務的用戶端應該如何進行異常處理

WCF技術剖析之十一:異步操作在WCF中的應用(上篇)

WCF技術剖析之十一:異步操作在WCF中的應用(下篇)

WCF技術剖析之十二:資料契約(Data Contract)和資料契約序列化器(DataContractSerializer)

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

WCF技術剖析之十四:泛型資料契約和集合資料契約(上篇)

WCF技術剖析之十四:泛型資料契約和集合資料契約(下篇)

WCF技術剖析之十五:資料契約代理(DataContractSurrogate)在序列化中的作用

WCF技術剖析之十六:資料契約的等效性和版本控制