Seguindo as funcionalidades fornecidas pela WCF Web API, este artigo começa a abordar os pontos de estensibilidade que a mesma fornece, para que assim possamos interceptar vários estágios do processamento da mensagem no servidor, bem como do lado do cliente, caso estejamos utilizando a classe HttpClient para consumo de um serviço.
A Microsoft está construindo essa API em cima do próprio WCF, aproveitando a flexibilidade que ele fornece, para assim tornar a construção de um serviço que utiliza os princípios REST e, consequentemente, o protocolo HTTP, em algo muito mais simples. Já pudemos perceber isso nos artigos anteriores, e o fato da criação desta nova API, também há situações onde precisamos de algumas customizações, o que obriga a Microsoft a colocar nesta mesma API, recursos para que seja possível tal customização, sem complicar tanto como antes, afinal, estamos “um nível acima”.
Como sabemos, o WCF extrai os bytes correspondentes a solicitação no protocolo, utilizando toda sua infraestrutura, que a partir de agora, fica totalmente encapsulada nesta nova API. Depois de capturado, o WCF encaminha essa solicitação para esta nova layer, e a partir de agora inicia o um novo pipeline, que está dentro desta API. É este pipeline que possui vários pontos, e o primeiro deles é chamado de Message Handlers. Como o próprio nome diz, eles estão logo no primeiro estágio do pipeline, ou seja, independentemente de qual sua intenção para com o serviço, elas serão sempre serão executadas, a menos que haja algum critério que você avalie e rejeite a solicitação, o que proibirá o avanço do processamento para os próximos handlers.
Basicamente, esses handlers recebem a instância de uma classe do tipo HttpRequestMessage, que traz toda a solicitação do usuário, e retornam a instância da classe HttpResponseMessage, contendo a resposta gerada para aquela solicitação. E como já ficou subententido, podemos ter vários handlers adicionados ao pipeline, onde cada um deles pode ser responsável por executar uma tarefa distinta, como logging, autenticação, autorização, etc. A imagem abaixo ilustra esse fluxo:
A imagem se preocupa em mostrar a posição dos message handlers em relação ao serviço, mas ela está incompleta. Há ainda outros pontos entre o último message handler e a execução do serviço em si. O próximo passo é a criação da classe que representa o serviço, qual utilizamos uma implementação da interface IResourceFactory para customizar isso, ou até mesmo, acoplar um container de injeção de dependência, como vimos detalhadamente neste artigo.
A primeira ação para a criação que um message handler customizado, é herdar da classe abstrata DelegatingChannel. Essa classe recebe em seu construtor um objeto do tipo HttpMessageChannel. A finalidade deste objeto que é passado no construtor, é com o intuito de cada handler seja responsável por executar uma determinada tarefa, e depois passar para o próximo, ou seja, uma implementação do padrão Decorator. Antes de prosseguirmos com as explicações, vejamos um exemplo simples para logging:
public class LoggingMessageChannel : DelegatingChannel
{
public LoggingMessageChannel(HttpMessageChannel innerChannel)
: base(innerChannel) { }
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith(tr =>
{
var response = tr.Result;
this.LogMessage(response);
return response;
}, cancellationToken);
}
private void LogMessage(HttpResponseMessage response)
{
Debug.WriteLine(
string.Format(“Request:rn{0}rnResponse:rn{1}”,
response.RequestMessage, response));
}
}
Analisando o código acima, podemos perceber que sobrescrevemos o método SendAsync. Quando uma requisição chega para o serviço e temos um message handler adicionado, o método SendAsync dele é disparado. Note que como parâmetro recebemos uma instância da classe HttpRequestMessage, que como já está claro, reflete a requisição do cliente; em seguida, nós invocamos o método SendAsync da classe base (DelegatingChannel), e com isso, a requisição é encaminhada para o respectivo método no serviço que criamos, executando-o. O interessante é que o retorno deste método trata-se de uma instância da classe Task<HttpResponseMessage>, e com isso, permite a execução da tarefa para uma outra thread, sem bloquear a thread que iniciou a requisição, podendo esta ir atender uma outra requisição. Isso facilita bastante quando temos alguma operação de I/O, que exige um processamento mais complexo, o que resultará em mais tempo esperando pela resposta, e o uso do processamento assíncrono dará uma maior escalabilidade.
Este método está fazendo uso da classe Task<TResult>, que faz parte do modelo de programação assíncrona do .NET conhecido como TAP (Task Asynchronous Programming). Essa classe fornece um método chamado ContinueWith<TResult>, que me permite executar, também assincronamente, uma outra tarefa quando a tarefa principal for concluída, ou seja, no exemplo que temos acima, invocamos o método SendAsync da classe base, o método do serviço é disparado, e quando ele finalizar, irá executar a tarefa que colocamos dentro do método ContinueWith<TResult>, que nada mais é do que fazer o logging da mensagem de requisição e resposta.
Apesar de toda essa implementação, ela não funciona por si só. Precisamos adicioná-la à execução, e para isso, vamos recorrer ao objeto responsável pela configuração destes serviços, que é a classe HttpHostConfiguration. Essa classe possui um método chamado AddMessageHandlers, que permite informar um array de objetos do tipo Type, onde devemos mencionar as implementações (message handlers) da classe DelegatingChannel que desejamos adicionar à execução. Abaixo temos parte do arquivo Global.asax com a configuração para o serviço, e estamos fazendo uso de sua interface fluente para incrementarmos as customizações.
protected void Application_Start(object sender, EventArgs e)
{
var config =
HttpHostConfiguration.Create()
.SetResourceFactory<MEFResourceFactory>()
.SetErrorHandler<BadRequestErrorHandler>()
.AddMessageHandlers(typeof(LoggingMessageChannel));
RouteTable.Routes.MapServiceRoute<ServicoDeProspeccao>(“prospeccoes”, config);
}
Como já mostrei aqui, há também a possibilidade de utilizar essa mesma API em aplicações cliente, para o consumo destes tipos de serviços. E como era de se esperar, há também a possibilidade de fazer a interceptação da requisição e da resposta deste lado, e com isso, conseguir realizar verificações, consistências antes de enviar e/ou receber.
A classe HttpClient é a principal responsável por efetuar toda a coordenação das requisições e encaminhar as respectivas respostas para quem solicitou. Essa classe possui uma propriedade chamada Channel, que permite referenciar uma classe que herda da classe abstrata MessageProcessingChannel. Essa classe nos obriga a sobrescrever dois métodos, também autoexplicativos: ProcessRequest e ProcessResponse. O primeiro método é invocado momentos antes de enviar a requisição ao serviço, já o segundo, é invocado assim que a resposta volta. Abaixo temos uma implementação simples do mesmo:
public class LogProcessingChannel : MessageProcessingChannel
{
public LogProcessingChannel(HttpMessageChannel innerChannel)
: base(innerChannel) { }
protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
{
ConsoleLog(request);
return request;
}
protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
{
ConsoleLog(response);
return response;
}
}
Depois disso, tudo o que precisamos fazer é adicioná-la à classe HttpClient, definindo-a na propriedade Channel. Só que o construtor recebe a instância de uma classe que corresponde ao canal final, que neste caso é a classe HttpClientChannel, que faz uso interno das classes de baixo nível do namespace System.Net (HttpWebRequest e HttpWebResponse). Abaixo temos um exemplo de como proceder com essa configuração:
using (HttpClient client = new HttpClient(“http://localhost:1989/prospeccoes/”))
{
client.Channel = new LogProcessingChannel(new HttpClientChannel());
using (HttpRequestMessage request2 =
new HttpRequestMessage(HttpMethod.Get, “RecuperarEmpresasEmProspeccao”))
{
Console.WriteLine(client.Send(request2).Content.ReadAsString());
}
}
Percebemos que há uma certa discrepância nos nomes, pois em alguns lugares vemos este recurso mencionado como message handlers e em outros como message channels. Eu estou utilizando o que imagino ser a mais recente, mas é importante salientar que este projeto ainda está em sua fase inicial, e pode haver mudanças significativas no decorrer de seu desenvolvimento.