Como já é sabido, podemos expor um serviço WCF que utiliza tipos complexos, ou seja, uma classe customizada, contendo as propriedades que a descrevem. Com isso, basta referenciá-la em nosso contrato e, consequentemente, os tipos utilizados serão expostos nos metadados (WSDL) do serviço, permitindo aos clientes criar uma representação desta classe. Para exemplificar, eis a classe com a Interface que descreve o contrato do serviço:
[DataContract]
public class Usuario
{
[DataMember]
public string Nome { get; set; }
}
[ServiceContract]
public interface IContrato
{
[OperationContract]
Usuario Recuperar(Usuario u);
}
Imagine que por algum motivo, voce cria um novo tipo, chamado OutroUsuario e quer fazer o uso deste no contrato, mudando tanto o tipo do retorno quanto o tipo do parametro u do método Recuperar. Com esta mudança, o WSDL continuará referenciando o tipo antigo Usuario, não “quebrando” as referencias atuais. A idéia é alterar o tipo apenas do lado do servidor, não refletindo do lado do cliente. O código abaixo ilustra o tipo OutroUsuario, que será utilizado pelo serviço ao invés do tipo Usuario:
[DataContract(Name = “Usuario”)]
public class OutroUsuario
{
public string XNome { get; set; }
public string Dummy { get; set; }
}
[ServiceContract]
public interface IContrato
{
[OperationContract]
OutroUsuario Recuperar(OutroUsuario u);
}
Da forma que o contrato está, novas referencias utilizarão o tipo OutroUsuario e, além disso, os clientes atuais mandarão uma instancia de um tipo não conhecido pelo contrato (Usuario) e, não será possível efetuar a deserialização.
Felizmente o WCF disponibiliza um recurso chamado surrogate (substituto). Com a sua utilização, podemos interceptar e customizar a serialização, deserialização e a projeção dos metadados, podendo inclusive substituir um tipo por outro. Para criar um surrogate, é necessário criar uma classe e implementar a Interface IDataContractSurrogate. Entre os principais métodos, temos: GetDataContractType, GetDeserializedObject e GetObjectToSerialize.
O método GetDataContractType é responsável por mapear um tipo em outro, sendo invocado na serialização, deserialização e na importação e exportação dos metadados do serviço. Já o método GetDeserializedObject é invocado quando acontecer a deserialização de um objeto (cliente para o serviço). E, finalmente, o método GetObjectToSerialize é invocado apenas na serialização do objeto (serviço para o cliente). Abaixo temos a implementação do surrogate para exemplo:
public class SurrogateParaUsuario : IDataContractSurrogate
{
public Type GetDataContractType(Type type)
{
return type == typeof(OutroUsuario) ? typeof(Usuario) : type;
}
public object GetDeserializedObject(object obj, Type targetType)
{
Usuario u = obj as Usuario;
if (u != null)
return new OutroUsuario() { Dummy = “ValorPadrao”, XNome = u.Nome };
return obj;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
OutroUsuario o = obj as OutroUsuario;
if (o != null)
return new Usuario() { Nome = o.XNome + o.Dummy };
return obj;
}
//outros métodos
}
Quando um cliente invocar a operação Recuperar, ele passará a instancia de uma classe Usuario. Quando a mensagem chegar até o serviço, o método GetDataContractType será executado, devendo retornar um Type que representará o tipo em que o objeto deverá ser convertido que, no nosso caso, será o tipo OutroUsuario. Esse método é executado para cada tipo encontrado na operação, descartando os tipos primitivos (string, int, etc.).
Ao analisar o método GetDeserializedObject vemos que a instancia do parametro obj é do tipo Usuario e, neste momento, fazemos o mapeamento do tipo Usuario para o tipo OutroUsuario, mantendo os valores das propriedades fornecidas pelo cliente. Já no método GetObjectToSerialize fazemos o processo inverso, já que o nosso cliente espera a instancia de um objeto Usuario.
Essa classe por si só não funciona. Existe um behavior chamado DataContractSerializerOperationBehavior que nos permite interagir com o serializador do WCF. Ele por sua vez, fornece uma propriedade chamada DataContractSurrogate que recebe uma instancia de uma classe que implemente a Interface IDataContractSurrogate e deverá ser configurada antes da abertura do host, como mostrado abaixo:
host.AddServiceEndpoint(typeof(IContrato), new WSHttpBinding(), “srv”);
OperationDescription od = host.Description.Endpoints[0].Contract.Operations[0];
od.Behaviors.Find<DataContractSerializerOperationBehavior>().DataContractSurrogate =
new SurrogateParaUsuario();
host.Open();