[愛心連結:拯救一個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>
注:部分内容節選自《WCF技術剖析(卷1)》第四章:序列化與資料契約(Serialization and Data Contract)
WCF技術剖析之十一:異步操作在WCF中的應用(上篇) WCF技術剖析之十一:異步操作在WCF中的應用(下篇) WCF技術剖析之十二:資料契約(Data Contract)和資料契約序列化器(DataContractSerializer) WCF技術剖析之十三:序列化過程中的已知類型(Known Type) WCF技術剖析之十四:泛型資料契約和集合資料契約(上篇) WCF技術剖析之十四:泛型資料契約和集合資料契約(下篇) WCF技術剖析之十五:資料契約代理(DataContractSurrogate)在序列化中的作用 WCF技術剖析之十六:資料契約的等效性和版本控制 |