WCF 中的序列化是用DataContractSerializer,所有被[DataContract]和 [DataMemeber]標記的類和屬性會被DataContractSerializer序列化。在WCF中使 用Contract模式來分辨和指定序列化/反序列化的類型,它是通過 http://xmlns/Class這樣的命名空間來標識這個序列化的對象的,一旦在序列化 過程中無法找到這樣的標識(比如某個字段,或者子對象)時,序列化就會失敗 。KnownTypeAttribute就提供了為我們通知序列化器去尋找未知對象的映射的途 徑。在Remoting中這樣的問題不會存在,因為Remoting實際上是通過將一個類型 傳遞給雙方來進行類型匹配的。
那麼KnowTypeAttribute到底用在什麼地 方呢?上邊說了,當前類的未知類型。那什麼又是當前類的未知類型呢?或許說 未知類型有些不恰當,但下邊的實際應用可能會讓你更清楚這到底是指什麼。
1). 序列化對象是從期望返回的類型繼承;
2). 無法確定當前所 使用類型的。例如Object類型,或者接口類型,你需要告訴序列化器去尋找確切 的類來進行序列化。
3). 使用泛型類型作為期望返回類型的;
4). 使用像ArrayList等以object為基礎類型存儲對象的;
下邊 我們以第一種類型為例說明KnownTypeAttribute的用法。序列化對象一般是參與 到在服務端和客戶端傳遞的數據。在面向對象的設計中,繼承可以很好的解決很 多業務問題,並簡化處理。而在下邊的例子中我們可以看出KnownType到底能夠 做什麼。
namespace WcfServiceDemo
{
[DataContract]
public class Address
{
public Address()
{
……
}
[DataMember]
public string AddressCategory { get; set; }
[DataMember]
public string AddressTitle { get; set; }
[DataMember]
public string AddressDetail { get; set; }
}
[DataContract]
public class BusinessAddress : Address
{
……
}
[DataContract]
public class HomeAddress : Address
{
……
}
}
public class Service1 : IService1
{
public Address GetAddress(bool isHome)
{
if (isHome)
return new HomeAddress();
else
return new BusinessAddress();
}
}
上邊的代碼中簡單的生命了三個數據契約類型,Address作為基 類,HomeAddress和BusinessAddress繼承於Address類。在Service實現中,簡單 的通過一個參數來判斷到底返回哪個子類。我們可以簡單的寫個客戶端來調用一 下這個服務,但很快你會發現浏覽器(客戶端)返回給你了錯誤:
Server Error in '/' Application.
The underlying connection was closed: The connection was closed unexpectedly.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Net.WebException: The underlying connection was closed: The connection was closed unexpectedly.
很顯然,這個錯誤並不能清楚的反應具體發生了 什麼,如果你跟進服務你會發現,所有的操作都正常,只是在返回數據的時候發 生了錯誤。如果你願意,你可以用DataContractSerializer類的一些方法來將一 個HomeAddress序列化並反序列化為一個Address類型,你就會看到這個過程中發 生了錯誤,序列化都無法進行。為什麼呢?原因是 HomeAddress/BusinessAddress的類型標識(如 http://www.demo.com/BusinessAddress)無法序列化Address類型時匹配,它就 不知道該把它序列化成哪個確切的類型。
解決方法,給Address添加KnownTypeAttribute標識,當一個HomeAddress對 象或者BusinessAddress對象被傳遞到Address進行序列化時,序列化器認識這個 對象並能根據契約來進行序列化。
[DataContract]
[KnownType(typeof(BusinessAddress))]
[KnownType(typeof (HomeAddress))]
public class Address
{
……
}
再次調用客戶端(注意:如果你是通 過Service Reference來引用服務的,那你必須在編譯完服務端後選擇Update Service Reference來更新服務引用,否則你的變化不會反應到客戶端調用), 現在你應該可以看到結果了。對於KnownTypeAttribute它還有一個可以替換的選 擇ServiceKnownTypeAttribute,你可以將它應用到一個Service或者一個 Operation(他們的區別是:當把ServiceKnownType標記給以個Service,那麼在 這個Service內KnownType都發生作用;而對於Operation則只在這個Operation時 有效,即作用域不同。)。以下代碼同樣解決了上述問題:
[OperationContract]
[ServiceKnownType(typeof(BusinessAddress))]
[ServiceKnownType(typeof(HomeAddress))]
Address GetAddress (bool isHome);
KnownType的另外一種標識方式是不用加代碼的 ,在配置文件中通過聲明型編程實現:
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="WcfServiceDemo.Address, MyAssembly, Version=2.0.0.0, Culture=neutral,PublicKeyToken=null, processorArchitecture=MSIL">
<knownType type="MyCompany.Library.Circle, MyAssembly, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null, processorArchitecture=MSIL"/>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
而對於泛型來說, KnownType是不能被直接應用的,如果你寫成[KnownType(typeof (BusinessAddress<>))]這樣是不允許,但CLR給我們提供了另外一種方法 來實現對序列化的通知:
[KnownType ("GetKnownTypes")]
public class Address<T>
{
static Type[] GetKnownTypes()
{
return new Type[] { typeof(HomeAddress<T>) ,typeof (BusinessAddress)};
}
}
我們指定尋找 KnownType時到GetKnownTypes方法去找,GetKnownTypes方法返回一個告知序列 化器映射類型的數組。既然我們可以用方法,那是否意味著我們也可以動態的來 定義GetKnownTypes返回的類型集合呢?當然,在上邊的例子中你可以通過擴展 GetKnownTypes方法來實現。但為了DataContract類型定義的簡約和獨立性,我 們不妨將這個函數摘出來,或許更有利於程序結構:
[ServiceContract]
[ServiceKnownType ("GetKnownTypes", typeof(KnownTypesProvider))]
public interface IService1
{
……
}
static class KnownTypesProvider
{
static Type[] GetKnownTypes(ICustomAttributeProvider knownTypeAttributeTarget)
{
Type contractType = (Type)knownTypeAttributeTarget;
return contractType.GetGenericArguments() ;
}
}
我 們將類型通知提供者定義到函數外部,並通過利用ServiceKnownType的重載構造 函數傳入需要去尋找的通知類提供者及其方法。這樣你很容易擴展這個函數並動 態加載通知類型。
你仍然可以通過使用配置文件的方式來達到同樣的功 能:
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="MyCompany.Library.Shape,
MyAssembly, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=XXXXXX, processorArchitecture=MSIL">
<knownType type="MyCompany.Library.Circle,
MyAssembly, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=XXXXXX, processorArchitecture=MSIL">
<parameter type="System.Collections.Generic.Dictionary">
<parameter type="System.String"/>
<parameter index="0"/>
</parameter>
</knownType>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
以上代碼就指定了將 Circle<Dictionary<string, T>>作為Shape<T>的一個 knownType。注意,當你指定某個knownType可能用到多於一個參數為泛型對象的 參數時,通過index來指定與期望類型T不同的另外一個類型。例如上邊的代碼中 指定了第一個參數為String類型(通過index指定),第二個參數T與 Shape<T>的T相同,不用特別指定。