20 de julho de 2011

Usando um Web Service no Visual Studio - parte II

Escrevi um post em 2009 explicando alguns detalhes sobre o funcionamento de Web Services e mostrando como usá-los em um projeto do Visual Studio. Aquele post descrevia como usar o IDE para gerar automaticamente uma classe proxy, necessária para fazermos a chamada das funções remotas disponibilizadas pelo serviço. Tal classe é chamada de "proxy" por ser projetada segundo o design pattern que leva esse nome.

Como se vê no post, gerar o proxy é um procedimento muito simples, assim como utilizá-lo para executar a função remota. Para a grande maioria das situações, usar o Web Service desse modo é suficiente. No entanto, há uma desvantagem na forma como isso é feito. Uma vez que o processo todo é automatizado e o trâmite da comunicação com o servidor fica escondido nas bibliotecas da plataforma .Net, não é possível customizar parte alguma da comunicação.

Mas, porque seria necessário interceptar a comunicação e alterar alguma coisa ? Uma razão óbvia é depuração. Isso é, se acontecer um erro, é interessante saber quais foram os comandos enviados e os respectivos retornos fornecidos pelo servidor. Um sniffer não conseguiria fazer esse serviço caso o web service esteja num canal seguro (HTTPS) pois as informações são criptografadas.

Uma outra razão seria customizar a serialização das informações, oportunidade para enxugar o texto trafegado, removendo-se partes sobressalentes. Ou, ainda, para adequar a formatação dos parâmetros da função que são trafegados. Do ponto de vista do proxy, a serialização é o processo pelo qual o nome da função remota e os parâmetros que passamos para ela são transformados em texto e empacotados num XML antes de serem enviados para execução no servidor. Ao receber uma resposta do servidor, o proxy tem que aplicar o processo inverso no XML recebido, isto é, fazer a desserialização, convertendo o conteúdo do XML em tipos de dados nativos da linguagem e associando-o ao valor de retorno da função. Lembre-se que o protocolo usado para comunicação com web services é o SOAP, que é baseado em XML. Portanto, um parâmetro com tipo de dado complexo pode exigir uma formatação especial pra funcionar.

Já que o código gerado automaticamente pelo IDE do Visual Studio não permite interceptar a serialização, teremos que optar por um método alternativo. O SDK do .NET vem com uma ferramenta de linha de comando chamada WSDL.EXE. Basicamente, nela você informa o arquivo descritor do Web Service (WSDL) e ela gera a classe proxy apropriada. O quadro abaixo mostra um exemplo básico de como usar a ferramenta:
wsdl /language:CS /n:"WNameSpace" WMeuServico.wsdl

Nesse exemplo, o WSDL.EXE interpretará a descrição contida no arquivo WMeuServico.wsdl e gerará um uma classe proxy em C# inserindo-a no namespace WNameSpace. A diferença em relação ao proxy gerado diretamente pelo Visual Studio está na infraestrutura de classes envolvidas. No caso do WSDL.EXE, o proxy é uma herança de SoapHttpClientProtocol. A execução dos métodos dessa classe pode ser extendida usando-se atributos que implementem as funções contidas em SoapExtension. Ou seja, cria-se uma herança de SoapExtension que então é aplicada como um atributo da função existente no Web Service. Quando a função remota é chamada, a serialização passa a ser controlada pela nossa versão do SoapExtension.

O cerne dessa solução é o método ProcessMessage. Ele recebe uma instância com a mensagem SOAP atual, permitindo manipulá-la conforme a necessidade. O exemplo a seguir cria um log com as mensagens trafegadas no Web Service:
public class TWTraceExtension : SoapExtension
{
/* ... */
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
StreamWriter w = new StreamWriter(_LogFile);
w.WriteLine ("=> Serializado");
w.Flush();
message.Stream.Position = 0;
Copy(message.Stream, fs);
w.Close();
break;
case SoapMessageStage.BeforeDeserialize:
StreamWriter w = new StreamWriter(_LogFile);
w.WriteLine ("=> Desserializado");
w.Flush();
message.Stream.Position = 0;
Copy(message.Stream, fs);
w.Close();
break;
case SoapMessageStage.AfterDeserialize:
break;
}
}
/* ... */
}

Agora que temos a extensão do processo de serialização e desserialização, precisamos criar um atributo que seja aplicável ao método do Web Service. Fazemos isso criando uma extensão da classe de atributo SoapExtensionAttribute, cujo objetivo principal é determinar qual classe deve ser usada para serializar. Veja um exemplo:
[AttributeUsage(AttributeTargets.Method)]
public class TWTraceExtensionAttribute : SoapExtensionAttribute
{
/* ... */
public override Type ExtensionType
{
get { return typeof(TWTraceExtension); }
}
/* ... */
}

Veja que a propriedade ExtensionType é sobrescrita para retornar informações sobre nossa classe de serialização. Um outro detalhe é que a declaração de nossa classe de atributo é ela própria configurada por um atributo indicativo do tipo de uso esperado para ela : ser atributo de métodos de outras classes.

Por fim, devemos usar o atributo criado acima para configurar o método do Web Service que vamos utilizar. Isso implica modificar o fonte gerado automaticamente pelo WSDL; então, tal configuração terá que ser refeita se precisarmos gerar o fonte de novo do Web Service. Veja um exemplo de configuração:
/* ... */
public partial class Service1 : System.Web.Services.Protocols.SoapHttpClientProtocol
{
/* ... */
[TWTraceExtensionAttribute(Filename = "c:\\log.txt")]
public string consultaNFe ([System.Xml.Serialization.XmlAnyElementAttribute()]
System.Xml.XmlElement Any)
{
/* ... */
}

A mesma solução pode ser adotada para a classe servidora do Web Service, situação em que estaremos interceptando as mensagens que um Web Service desenvolvido por nós está trocando.

Um exemplo completo de customização do SoapExtension pode ser encontrado na documentação dessa classe no MSDN. No exemplo, as mensagens trafegadas são armazenadas num arquivo de log.

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.