No artigo anterior eu falei sobre os message handlers, que afetam todas as mensagens que chegam à um determinado serviço, e que podemos utilizá-los para implementar alguns recursos como autenticação, autorização, etc. Só que também temos cenários onde queremos afetar apenas uma determinada operação, fornecendo informações que talvez tenham sido omitidas na requisição, eventuais validações, etc.
Com isso, temos mais um ponto de estensibilidade no WCF Web API chamado de Operation Handlers. Depois da requisição passar pelos eventuais Message Handlers e da criação da classe que representa o serviço, momentos antes de executar a requisição solicitada, entram em cena os Request Operation Handlers. Na sequência, executa a requisição solicitada, e antes de devolver para o cliente, executa também os Response Operation Handlers, caso eles existam. Como podemos perceber, os Operation Handlers estão divididos em duas partes, onde temos aqueles que são executados antes da requisição ser executada, e aqueles que são executados depois que a requisição foi atendida. A imagem abaixo ilustra esse fluxo:
A implementação deste handler consiste em criar uma classe que herda da classe abstrata HttpOperationHandler. Essa classe fornece duas propriedades principais: InputParameters e OutputParameters. Cada uma dessas propriedades são representadas por uma coleção de somente leitura, onde cada parâmetro é representado por um objeto do tipo HttpParameter. Se analisarmos o modelo de classes, veremos que existem 16 versões da classe HttpOperationHandler, onde o que muda é a quantidade de parâmetros genéricos, que representarão os parâmetros de entrada da operação e um deles corresponderá ao resultado da mesma, e os handlers interferem indiretamente na criação e definição dos valores destas coleções.
Para exemplificar o seu uso, criaremos um operation handler que define algum header da requisição como sendo parâmetro para o método, entregando a informação de uma forma mais simples, deixando a extração da informação do header da requisição à cargo do handler. Abaixo vemos o serviço:
[ServiceContract]
public class ServicoDeTeste
{
[WebGet]
public HttpResponseMessage Informacoes(string codigo)
{
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
Content = new ObjectContent<Informacao>(new Informacao(){ Codigo = codigo, Dado = “Alguma Info” })
};
}
}
Como podemos perceber, o método Informacoes espera um parâmetro chamado codigo, que o cliente não mandará como parte da Uri, mas sim como um header customizado na requisição. O nosso operation handler irá extrair esse header e irá “injetar” o valor no parâmetro esperado pelo método. Utilizaremos a versão da classe HttpOperationHandler que recebe dois parâmetros genéricos, onde o primeiro corresponde ao parâmetro de entrada, e o segundo, corresponde ao tipo do parâmetro que o handler está tratado.
Recebemos no construtor o nome do parâmetro que deve ser adicionado à coleção de InputParameters, e o nome do header que esse handler deverá extrair da requisição. O(s) parâmetro(s) do método OnHandle e o tipo de retorno são definidos de acordo com os parâmetros genéricos que foram definidos na construção da classe HttpOperationHandler. Esse método é disparado pelo runtime, e é onde devemos colocar o tratamento necessário. Para o exemplo, se não encontrarmos o header na requisição, uma exceção deverá ser disparada, informando que a requisição é inválida. Abaixo temos o código que corresponde a esta implementação:
public class HeaderAsParameterHandler : HttpOperationHandler<HttpRequestMessage, string>
{
private readonly string headerName;
public HeaderAsParameterHandler(string outputParameterName, string headerName)
: base(outputParameterName)
{
this.headerName = headerName;
}
public override string OnHandle(HttpRequestMessage input)
{
if (!input.Headers.Contains(headerName))
throw new HttpResponseException(HttpStatusCode.BadRequest);
return input.Headers.GetValues(headerName).First();
}
}
Da mesma forma que temos um handler para extrair o header da requisição e injetar na coleção de parâmetros, vamos exemplificar também a volta, ou seja, capturar alguma informação do resultado, para definirmos também como um header customizado na resposta. Agora herdaremos da classe HttpOperationHandler, e definiremos os parâmetros de entrada e saída como sendo do tipo HttpResponseMessage, pois ela é o alvo da alteração e também de onde extrairemos as informações.
public class OutputAsHeaderHandler : HttpOperationHandler<HttpResponseMessage, HttpResponseMessage>
{
private readonly string headerName;
private readonly Func<HttpResponseMessage, string> extractor;
public OutputAsHeaderHandler(string headerName, Func<HttpResponseMessage, string> extractor)
: base(“response”)
{
this.headerName = headerName;
this.extractor = extractor;
}
public override HttpResponseMessage OnHandle(HttpResponseMessage input)
{
input.Headers.AddWithoutValidation(this.headerName, this.extractor(input));
return input;
}
}
Note que o construtor da classe acima, recebe o nome do header a ser inserido na resposta, e um delegate que permite ao consumidor da classe, extrair a informação do lugar que ele achar mais conveniente. No método OnHandle, capturamos a mensagem de resposta, invocamos o delegate e extraimos o valor do header e, finalmente, retornamos a mensagem já com o header devidamente adicionado.
Depois dos handlers criados, precisamos incorporá-los na execução. Para isso, vamos recorrer à uma outra classe, chamada de HttpOperationHandlerFactory. Esse classe fornece dois métodos, onde cada um corresponde à criação de handlers para requisição (OnCreateRequestHandlers) e outro para a resposta (OnCreateResponseHandlers). Como temos dois handlers, um que trata a requisição e outro que trata a resposta, então vamos sobrescrever ambos os métodos, assim como é mostrado abaixo:
public class HeaderMapperOperationHandlerFactory : HttpOperationHandlerFactory
{
protected override Collection<HttpOperationHandler> OnCreateRequestHandlers(ServiceEndpoint endpoint, HttpOperationDescription operation)
{
var handlers = base.OnCreateRequestHandlers(endpoint, operation);
handlers.Add(new HeaderAsParameterHandler(“codigo”, “CodigoDoCliente”));
return handlers;
}
protected override Collection<HttpOperationHandler> OnCreateResponseHandlers(ServiceEndpoint endpoint, HttpOperationDescription operation)
{
var handlers = base.OnCreateResponseHandlers(endpoint, operation);
handlers.Add(new OutputAsHeaderHandler(“CodigoDoCliente”,
response => ((ObjectContent<Informacao>)response.Content).ReadAs().Codigo));
return handlers;
}
}
No método OnCreateRequestHandlers adicionamos a instância da classe HeaderAsParameterHandler, mapeando o header CodigoDoCliente para o parâmetro codigo. Já no método OnCreateResponseHandlers, criamos a instância da classe OutputAsHeaderHandler, informando no seu construtor o nome do header que desejamos que seja adicionado, e em seguida, um delegate que extrai o valor do header do corpo da mensagem.
Finalmente, tudo o que precisamos fazer é configurar o serviço para utilizar a factory de criação de operation handlers. Para isso, recorremos ao método genérico SetOperationHandlerFactory<T> da classe HttpHostConfiguration, onde informamos em seu tipo genérico T, o tipo da classe que criamos, que é a HeaderMapperOperationHandlerFactory. Abaixo temos um exemplo de como proceder com essa configuração:
protected void Application_Start(object sender, EventArgs e)
{
var config =
HttpHostConfiguration.Create().SetOperationHandlerFactory<HeaderMapperOperationHandlerFactory>();
RouteTable.Routes.MapServiceRoute<ServicoDeTeste>(“teste”, config);
}
Depois de toda essa customização e configuração, tudo o que nos resta é realizar um teste. Ao efetuarmos uma requisição, vamos incluir o header customizado CodigoDoCliente, e o resultado deve voltar também com este mesmo header informado. Abaixo temos o log da requisição e da resposta que foi capturado pelo Fiddler:
Requisição
GET http://localhost:1989/teste/Informacoes HTTP/1.1
User-Agent: Fiddler
Host: localhost:1989
Accept: application/xml
CodigoDoCliente: 1291
Resposta
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Mon, 02 May 2011 02:09:22 GMT
X-AspNet-Version: 4.0.30319
CodigoDoCliente: 1291
Content-Length: 113
Cache-Control: private
Content-Type: application/xml; charset=utf-8
Connection: Close
<?xml version=”1.0″ encoding=”utf-8″?>
<Informacao>
<Dado>Alguma Info Aqui</Dado>
<Codigo>1291</Codigo>
</Informacao>