DataContract Surrogate

DataContract 範例示範如何使用資料合約 Surrogate 類別來自訂序列化、還原序列化、結構描述匯出和結構描述匯入等流程。 此範例示範如何在用戶端和伺服器情節中使用 Surrogate,其中資料會序列化並在 Windows Communication Foundation (WCF) 用戶端與服務之間傳輸。

注意

此範例的安裝程序與建置指示位於本主題的結尾。

此範例使用下列服務合約:

[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
[AllowNonSerializableTypes]
public interface IPersonnelDataService
{
    [OperationContract]
    void AddEmployee(Employee employee);

    [OperationContract]
    Employee GetEmployee(string name);
}

AddEmployee 作業允許使用者新增新進員工的資料,而 GetEmployee 作業則支援根據名字搜尋員工。

這些作業使用下列資料型別:

[DataContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
class Employee
{
    [DataMember]
    public DateTime dateHired;

    [DataMember]
    public Decimal salary;

    [DataMember]
    public Person person;
}

Employee 型別中,由於 Person 類別 (如下列範例程式碼所示) 不是有效的資料合約類別,因此 DataContractSerializer 無法加以序列化。

public class Person
{
    public string firstName;

    public string lastName;

    public int age;

    public Person() { }
}

您可以將 DataContractAttribute 屬性套用至 Person 類別,但是不一定可行。 例如,Person 類別可能是在您無法控制的其他組件中定義的。

在這個限制下,有一種方法可以序列化 Person 類別,即是使用標示為 DataContractAttribute 的另一個類別來替代它,然後將必要的資料整個複製到新類別。 這麼做的目的是要將 Person 類別對 DataContractSerializer 顯示為 DataContract。 請注意,這只是序列化非資料合約類別的其中一種方法。

此範例邏輯上是使用名為 Person 的不同類別來取代 PersonSurrogated 類別。

[DataContract(Name="Person", Namespace = "http://Microsoft.ServiceModel.Samples")]
public class PersonSurrogated
{
    [DataMember]
    public string FirstName;

    [DataMember]
    public string LastName;

    [DataMember]
    public int Age;
}

但實際上是使用資料合約 Surrogate 來達成這項取代。 資料合約 Surrogate 是實作 IDataContractSurrogate 的類別。 在這個範例中,AllowNonSerializableTypesSurrogate 類別會實作這個介面。

在介面實作中,第一項工作就是建立從 PersonPersonSurrogated 的型別對應。 序列化階段與結構描述匯出階段都會用到這個型別對應。 這種對應可以藉由實作 GetDataContractType(Type) 方法來達成。

public Type GetDataContractType(Type type)
{
    if (typeof(Person).IsAssignableFrom(type))
    {
        return typeof(PersonSurrogated);
    }
    return type;
}

GetObjectToSerialize(Object, Type) 方法會在序列化期間,將 Person 執行個體對應至 PersonSurrogated 執行個體,如下列範例程式碼所示。

public object GetObjectToSerialize(object obj, Type targetType)
{
    if (obj is Person)
    {
        Person person = (Person)obj;
        PersonSurrogated personSurrogated = new PersonSurrogated();
        personSurrogated.FirstName = person.firstName;
        personSurrogated.LastName = person.lastName;
        personSurrogated.Age = person.age;
        return personSurrogated;
    }
    return obj;
}

GetDeserializedObject(Object, Type) 方法會提供反向對應以進行還原序列化,如下列範例程式碼所示。

public object GetDeserializedObject(object obj,
Type targetType)
{
    if (obj is PersonSurrogated)
    {
        PersonSurrogated personSurrogated = (PersonSurrogated)obj;
        Person person = new Person();
        person.firstName = personSurrogated.FirstName;
        person.lastName = personSurrogated.LastName;
        person.age = personSurrogated.Age;
        return person;
    }
    return obj;
}

為了在結構描述匯入期間將 PersonSurrogated 資料合約對應至現有 Person 類別,範例會實作 GetReferencedTypeOnImport(String, String, Object) 方法,如下列範例程式碼所示。

public Type GetReferencedTypeOnImport(string typeName,
               string typeNamespace, object customData)
{
if (
typeNamespace.Equals("http://schemas.datacontract.org/2004/07/DCSurrogateSample")
)
    {
         if (typeName.Equals("PersonSurrogated"))
        {
             return typeof(Person);
        }
     }
     return null;
}

下列範例程式碼會完成 IDataContractSurrogate 介面的實作。

public System.CodeDom.CodeTypeDeclaration ProcessImportedType(
          System.CodeDom.CodeTypeDeclaration typeDeclaration,
          System.CodeDom.CodeCompileUnit compileUnit)
{
    return typeDeclaration;
}
public object GetCustomDataToExport(Type clrType,
                               Type dataContractType)
{
    return null;
}

public object GetCustomDataToExport(
System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
    return null;
}
public void GetKnownCustomDataTypes(
        KnownTypeCollection customDataTypes)
{
    // It does not matter what we do here.
    throw new NotImplementedException();
}

在這個範例中,AllowNonSerializableTypesAttribute 屬性在 ServiceModel 中啟用了 Surrogate。 依上述 IPersonnelDataService 服務合約看來,開發人員或許會想要在他們的服務合約上套用這個屬性, 因為這個屬性會實作 IContractBehavior,並在 ApplyClientBehaviorApplyDispatchBehavior 方法中設定作業上的 Surrogate。

但對本例而言,這個屬性並非必要,它只是在範例中做示範之用。 使用者也可以選擇使用程式碼或組態,透過手動方式新增類似的 IContractBehaviorIEndpointBehaviorIOperationBehavior 來啟用 Surrogate。

IContractBehavior 實作會尋找使用 DataContract 的作業,而尋找的方式是檢查這些作業是否註冊了行為 DataContractSerializerOperationBehavior。 如果沒有,就會在該行為上設定 DataContractSurrogate 屬性。 下列範例程式碼示範如何做到這點。 在這個作業行為上設定 Surrogate,就可以啟用 Surrogate 來進行序列化和還原序列化。

public void ApplyClientBehavior(ContractDescription description, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime proxy)
{
    foreach (OperationDescription opDesc in description.Operations)
    {
        ApplyDataContractSurrogate(opDesc);
    }
}

public void ApplyDispatchBehavior(ContractDescription description, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatch)
{
    foreach (OperationDescription opDesc in description.Operations)
    {
        ApplyDataContractSurrogate(opDesc);
    }
}

private static void ApplyDataContractSurrogate(OperationDescription description)
{
    DataContractSerializerOperationBehavior dcsOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
    if (dcsOperationBehavior != null)
    {
        if (dcsOperationBehavior.DataContractSurrogate == null)
            dcsOperationBehavior.DataContractSurrogate = new AllowNonSerializableTypesSurrogate();
    }
}

此外,還必須執行其他步驟,才能將 Surrogate 插入以供中繼資料產生期間使用。 有一個機制可以用來達到這個目的,即提供 IWsdlExportExtension,而這也是本範例所示範的。 另一個方法則是直接修改 WsdlExporter

AllowNonSerializableTypesAttribute 屬性會實作 IWsdlExportExtensionIContractBehavior。 在此案例中,延伸模組可以是 IContractBehaviorIEndpointBehaviorIWsdlExportExtension.ExportContract 方法實作會將此延伸新增至 DataContract 結構描述產生期間所使用的 XsdDataContractExporter,以啟用 Surrogate。 下列程式碼片段示範如何執行這項操作。

public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
{
    if (exporter == null)
        throw new ArgumentNullException("exporter");

    object dataContractExporter;
    XsdDataContractExporter xsdDCExporter;
    if (!exporter.State.TryGetValue(typeof(XsdDataContractExporter), out dataContractExporter))
    {
        xsdDCExporter = new XsdDataContractExporter(exporter.GeneratedXmlSchemas);
        exporter.State.Add(typeof(XsdDataContractExporter), xsdDCExporter);
    }
    else
    {
        xsdDCExporter = (XsdDataContractExporter)dataContractExporter;
    }
    if (xsdDCExporter.Options == null)
        xsdDCExporter.Options = new ExportOptions();

    if (xsdDCExporter.Options.DataContractSurrogate == null)
        xsdDCExporter.Options.DataContractSurrogate = new AllowNonSerializableTypesSurrogate();
}

當您執行範例時,用戶端會在呼叫 AddEmployee 之後接著呼叫 GetEmployee,以檢查第一項呼叫是否成功。 GetEmployee 作業要求的結果會顯示在用戶端主控台視窗中。 GetEmployee 作業必須成功找到員工,然後才印出 "found"。

注意

這個範例示範如何插入 Surrogate 以進行序列化、還原序列化和中繼資料產生作業, 但是不示範如何插入 Surrogate,以從中繼資料產生程式碼。 若要查看如何使用 Surrogate 以插入用戶端程式碼產生作業的範例,請參閱自訂 WSDL 發行集範例。

若要安裝、建置及執行範例

  1. 確認您已執行 Windows Communication Foundation 範例的一次性安裝程序

  2. 若要建置 C# 版本的解決方案,請遵循建置 Windows Communication Foundation 範例中的指示進行。

  3. 若要在單一或多部電腦組態中執行此範例,請遵循執行 Windows Communication Foundation 範例中的指示進行。