Alternativa de DataContract

O Substituto do DataContract demonstra como processos como serialização, desserialização, exportação de esquema e importação de esquema podem ser personalizados, usando uma classe substituta de contrato de dados. Este exemplo mostra como usar um substituto em um cenário de cliente e servidor em que os dados são serializados e transmitidos entre um cliente e um serviço do WCF (Windows Communication Foundation).

Observação

O procedimento de instalação e as instruções de compilação desse exemplo estão no final deste tópico.

O exemplo usa o seguinte contrato de serviço:

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

    [OperationContract]
    Employee GetEmployee(string name);
}

A operação AddEmployee permite que os usuários adicionem dados sobre novos funcionários e a operação GetEmployee permite a pesquisa de funcionários de acordo com o nome.

Essas operações usam o seguinte tipo de dados:

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

    [DataMember]
    public Decimal salary;

    [DataMember]
    public Person person;
}

No tipo Employee, a classe Person (mostrada no código de exemplo a seguir) não pode ser serializada pelo DataContractSerializer, devido ao fato de não ser uma classe de contrato de dados válida.

public class Person
{
    public string firstName;

    public string lastName;

    public int age;

    public Person() { }
}

Você pode aplicar o atributo DataContractAttribute à classe Person, mas isso nem sempre é possível. Por exemplo, a classe Person pode ser definida em um assembly separado sobre o qual você não tem controle.

Dada essa restrição, uma maneira de serializar a classe Person é substituí-la por outra classe marcada com DataContractAttribute e copiar os dados necessários para a nova classe. O objetivo é fazer com que a classe Person seja exibida como um DataContract para o DataContractSerializer. Observe que essa é uma maneira de serializar as classes de contrato que não são de dados.

O exemplo substitui logicamente a classe Person por uma classe diferente chamada PersonSurrogated.

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

    [DataMember]
    public string LastName;

    [DataMember]
    public int Age;
}

A alternativa do contrato de dados é usada para realizar essa substituição. Uma alternativa do contrato de dados é uma classe que implementa a IDataContractSurrogate. Neste exemplo, a classe AllowNonSerializableTypesSurrogate implementa essa interface.

Na implementação da interface, a primeira tarefa é estabelecer um mapeamento de tipo de Person para PersonSurrogated. Isso é usado tanto no momento da serialização quanto no momento da exportação de esquema. Esse mapeamento é realizado implementando o método GetDataContractType(Type).

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

O método GetObjectToSerialize(Object, Type) mapeia uma instância Person para uma instância PersonSurrogated durante a serialização, conforme mostrado no código de exemplo a seguir.

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

O método GetDeserializedObject(Object, Type) fornece o mapeamento reverso para desserialização, conforme mostrado no código de exemplo a seguir.

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

Para mapear o contrato de dados PersonSurrogated para a classe existente Person durante a importação de esquema, o exemplo implementa o método GetReferencedTypeOnImport(String, String, Object), conforme mostrado no código de exemplo a seguir.

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

O código de exemplo a seguir conclui a implementação da interface 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();
}

Neste exemplo, a alternativa é habilitada no ServiceModel por um atributo chamado AllowNonSerializableTypesAttribute. Os desenvolvedores precisam aplicar esse atributo no contrato de serviço, conforme mostrado no contrato de serviço IPersonnelDataService acima. Esse atributo implementa o IContractBehavior e configura a alternativa nas operações nos métodos ApplyClientBehavior e ApplyDispatchBehavior.

O atributo não é necessário nesse caso – ele é usado para fins de demonstração neste exemplo. Por outro lado, os usuários podem habilitar uma alternativa ao adicionar manualmente um IContractBehavior, IEndpointBehaviorou IOperationBehavior semelhante, usando o código ou a configuração.

A implementação IContractBehavior procura operações que usam DataContract, verificando se elas têm um DataContractSerializerOperationBehavior registrado. Nesse caso, a DataContractSurrogate propriedade é definida nesse comportamento. O código de exemplo a seguir mostra como isso é feito. Definir a alternativa nesse comportamento de operação o habilita para serialização e desserialização.

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

Etapas adicionais precisam ser executadas para conectar a alternativa de uso durante a geração de metadados. Um mecanismo para fazer isso é fornecer um IWsdlExportExtension, que é o que este exemplo demonstra. Outra maneira é modificar o WsdlExporter diretamente.

O atributo AllowNonSerializableTypesAttribute implementa IWsdlExportExtension e IContractBehavior. A extensão pode ser um IContractBehavior ou IEndpointBehavior, nesse caso. A implementação do método IWsdlExportExtension.ExportContract habilita a alternativa adicionando-a ao XsdDataContractExporter usado durante a geração de esquema para DataContract. O snippet de código a seguir mostra como fazer isso.

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

Quando você executa o exemplo, o cliente chama o AddEmployee, seguido de uma chamada ao GetEmployee, para verificar se a primeira chamada foi bem-sucedida. O resultado da solicitação de operação do GetEmployee é exibido na janela do console do cliente. A operação GetEmployee deve localizar o funcionário com êxito e imprimir "encontrado".

Observação

Este exemplo mostra como conectar uma alternativa para serialização, desserialização e geração de metadados. Ele não mostra como conectar uma alternativa para geração de código a partir de metadados. Para ver um exemplo de como uma alternativa pode ser usada para conectar-se à geração de código do cliente, confira o exemplo Publicação personalizada do WSDL.

Para configurar, compilar, e executar o exemplo

  1. Verifique se você executou o Procedimento de instalação avulsa dos exemplos do Windows Communication Foundation.

  2. Para compilar a edição do C#, siga as instruções em Compilação dos exemplos do Windows Communication Foundation.

  3. Para executar o exemplo em uma configuração de um único computador ou de vários computadores, siga as instruções em Como executar os exemplos do Windows Communication Foundation.