Суррогат Контракта данных

В примере DataContract показано, как процессы, такие как сериализация, десериализация, экспорт схемы и импорт схемы, можно настроить с помощью суррогатного класса контракта данных. В этом примере показано, как использовать суррогат в сценарии клиента и сервера, где данные сериализуются и передаются между клиентом и службой 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 отображался как DataContract для DataContractSerializer. Обратите внимание, что это один из способов сериализации классов контрактов, отличных от данных.

Пример логически заменяет 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;
}

Суррогат контракта данных используется для осуществления этой замены. Суррогат контракта данных — это класс, реализующий IDataContractSurrogate. В этом примере AllowNonSerializableTypesSurrogate класс реализует этот интерфейс.

В реализации интерфейса первая задача — установить сопоставление типов между Person и PersonSurrogated. Это используется как во время сериализации, так и во время экспорта схемы. Это сопоставление достигается путем реализации 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();
}

В этом примере суррогат включен в ServiceModel атрибутом AllowNonSerializableTypesAttribute. Разработчикам потребуется применить этот атрибут к контракту службы, как показано в приведенном выше контракте IPersonnelDataService службы. Этот атрибут реализует IContractBehavior, а также настраивает суррогат для операций в методах ApplyClientBehavior и ApplyDispatchBehavior.

Атрибут не требуется в этом случае — он используется для демонстрационных целей в этом примере. Кроме того, пользователи могут вручную включить суррогат, добавив аналогичный IContractBehavior, IEndpointBehavior, или IOperationBehavior с помощью кода или конфигурации.

Реализация IContractBehavior ищет операции, использующие DataContract, проверяя, зарегистрированы ли они в DataContractSerializerOperationBehavior. Если они это делают, это устанавливает свойство DataContractSurrogate этого поведения. В следующем примере кода показано, как это сделать. Настройка суррогата в этом поведении операции позволяет использовать сериализацию и десериализацию.

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();
    }
}

Необходимо выполнить дополнительные действия, чтобы подключить суррогат для использования во время создания метаданных. Одним из механизмов реализации этого является предоставление IWsdlExportExtension, что демонстрирует данный пример. Другим способом является изменить WsdlExporter напрямую.

Атрибут AllowNonSerializableTypesAttribute реализует IWsdlExportExtension и IContractBehavior. Расширение может быть либо IContractBehavior, либо IEndpointBehavior в данном случае. Его реализация метода добавляет суррогат в используемый IWsdlExportExtension.ExportContract при генерации схем для DataContract. В следующем фрагменте кода показано, как это сделать.

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 должна успешно найти сотрудника и вывести "найдено".

Замечание

В этом примере показано, как подключить суррогат для сериализации, десериализации и создания метаданных. Он не показывает, как подключить суррогат для создания кода из метаданных. Чтобы увидеть пример того, как суррогат можно использовать для генерации клиентского кода, см. пример пользовательской публикации WSDL.

Настройка, сборка и запуск примера

  1. Убедитесь, что вы выполнили процедуру настройки One-Time для образцов Windows Communication Foundation.

  2. Чтобы создать выпуск решения на C#, следуйте инструкциям в статье "Создание примеров Windows Communication Foundation".

  3. Чтобы запустить пример в конфигурации с одним или несколькими компьютерами, следуйте инструкциям в запуска примеров Windows Communication Foundation.