WCF 使用 DataContractSerializer 進行序列化,所有標記 [DataContract] 和 [DataMember] 的類別與屬性皆會被序列化。WCF 採用契約模式識別序列化/反序列化類型,透過命名空間 (如 https://xmlns/Class) 標識物件。當序列化過程中找不到類型標識 (如某欄位或子物件) 時,序列化即會失敗。KnownTypeAttribute 正是為了解決此問題,提供通知序列化器尋找未知類型對應的機制。這在 Remoting 中不存在,因為 Remoting 直接傳遞類型進行匹配。
Object 類型或介面類型,需告知序列化器確切類別ArrayList 等以 object 為基礎類型儲存物件的集合定義基底類別 Address 及兩個衍生類別 BusinessAddress、HomeAddress,服務根據參數回傳不同子類別:
namespace WcfServiceDemo
{
[DataContract]
public class 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)
{
return isHome ? new HomeAddress() : new BusinessAddress();
}
}
呼叫服務時會拋出連線關閉錯誤,實際上是序列化失敗:HomeAddress/BusinessAddress 的類型標識無法匹配 Address 類型,序列化器不知該序列化為何種確切類型。
[DataContract]
[KnownType(typeof(BusinessAddress))]
[KnownType(typeof(HomeAddress))]
public class Address { ... }
[OperationContract]
[ServiceKnownType(typeof(BusinessAddress))]
[ServiceKnownType(typeof(HomeAddress))]
Address GetAddress(bool isHome);
差異在於:標記在 Service 上,該服務內所有操作生效;標記在 Operation 上,僅該操作生效。
<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) };
}
}
指定靜態方法 GetKnownTypes 回傳類型陣列。為保持型別定義簡潔,可將方法抽離至外部提供者:
[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。index 指定與預期類型 T 不同的額外泛型參數,上例中第一個參數指定為 String,第二個參數 T 與 Shape<T> 相同無需特別指定。