首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

深入解析 WCF DataContract KnownTypeAttribute 應用場景與技巧

     編輯:關於.NET

WCF 使用 DataContractSerializer 進行序列化,所有標記 [DataContract] 和 [DataMember] 的類別與屬性皆會被序列化。WCF 採用契約模式識別序列化/反序列化類型,透過命名空間 (如 https://xmlns/Class) 標識物件。當序列化過程中找不到類型標識 (如某欄位或子物件) 時,序列化即會失敗。KnownTypeAttribute 正是為了解決此問題,提供通知序列化器尋找未知類型對應的機制。這在 Remoting 中不存在,因為 Remoting 直接傳遞類型進行匹配。

KnownTypeAttribute 適用的四大場景

  1. 序列化物件繼承自預期回傳類型:基底類型回傳衍生類別實例
  2. 無法確定當前使用類型:如 Object 類型或介面類型,需告知序列化器確切類別
  3. 使用泛型類型作為預期回傳類型
  4. 使用 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 類型,序列化器不知該序列化為何種確切類型。

解決方案 1:在基底類別標記 KnownTypeAttribute

[DataContract]
[KnownType(typeof(BusinessAddress))]
[KnownType(typeof(HomeAddress))]
public class Address { ... }

解決方案 2:使用 ServiceKnownTypeAttribute (作用域更靈活)

[OperationContract]
[ServiceKnownType(typeof(BusinessAddress))]
[ServiceKnownType(typeof(HomeAddress))]
Address GetAddress(bool isHome);

差異在於:標記在 Service 上,該服務內所有操作生效;標記在 Operation 上,僅該操作生效。

解決方案 3:配置檔宣告式程式設計

<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,如 [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 多載建構函式傳入提供者類型與方法名,輕鬆動態載入已知類型。

配置檔中泛型 KnownType 宣告

<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> 相同無需特別指定。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved