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
, IEndpointBehavior
ou 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
Verifique se você executou o Procedimento de instalação avulsa dos exemplos do Windows Communication Foundation.
Para compilar a edição do C#, siga as instruções em Compilação dos exemplos do Windows Communication Foundation.
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.