Суррогат DataContract

В примере 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. Расширение может быть либо или IContractBehaviorIEndpointBehavior в данном случае. Реализация метода IWsdlExportExtension.ExportContract включает заменяющий класс путем его добавления в XsdDataContractExporter, используемый во время создания схемы для 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. Убедитесь, что вы выполнили процедуру однократной установки для примеров Windows Communication Foundation.

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

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