程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF技術剖析之十三:序列化過程中的已知類型(Known Type)

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

編輯:關於.NET

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>
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved