Manipuladores de mensagens HTTP no ASP.NET Web API

Um manipulador de mensagens é uma classe que recebe uma solicitação HTTP e retorna uma resposta HTTP. Manipuladores de mensagens derivam da classe HttpMessageHandler abstrata.

Normalmente, uma série de manipuladores de mensagens são encadeados. O primeiro manipulador recebe uma solicitação HTTP, faz algum processamento e fornece a solicitação para o próximo manipulador. Em algum momento, a resposta é criada e volta para a cadeia. Esse padrão é chamado de manipulador de delegação .

Diagrama de manipuladores de mensagens encadeados, ilustrando o processo para receber uma solicitação H T TP e retornar uma resposta H T TP.

manipuladores de mensagens Server-Side

No lado do servidor, o pipeline da API Web usa alguns manipuladores de mensagens internos:

  • HttpServer obtém a solicitação do host.
  • HttpRoutingDispatcher despacha a solicitação com base na rota.
  • HttpControllerDispatcher envia a solicitação para um controlador de API Web.

Você pode adicionar manipuladores personalizados ao pipeline. Manipuladores de mensagens são bons para preocupações transversais que operam no nível das mensagens HTTP (em vez de ações do controlador). Por exemplo, um manipulador de mensagens pode:

  • Ler ou modificar cabeçalhos de solicitação.
  • Adicione um cabeçalho de resposta às respostas.
  • Valide as solicitações antes que elas cheguem ao controlador.

Este diagrama mostra dois manipuladores personalizados inseridos no pipeline:

Diagrama de manipuladores de mensagens do lado do servidor, exibindo dois manipuladores personalizados inseridos no pipeline de API da Web.

Observação

No lado do cliente, o HttpClient também usa manipuladores de mensagens. Para obter mais informações, consulte Manipuladores de mensagens HttpClient.

Manipuladores de mensagens personalizados

Para escrever um manipulador de mensagens personalizado, derive de System.Net.Http.DelegatingHandler e substitua o método SendAsync . Esse método tem a seguinte assinatura:

Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken);

O método usa um HttpRequestMessage como entrada e retorna de forma assíncrona um HttpResponseMessage. Uma implementação típica faz o seguinte:

  1. Processe a mensagem de solicitação.
  2. Chame base.SendAsync para enviar a solicitação para o manipulador interno.
  3. O manipulador interno retorna uma mensagem de resposta. (Esta etapa é assíncrona.)
  4. Processe a resposta e retorne-a ao chamador.

Aqui está um exemplo trivial:

public class MessageHandler1 : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("Process request");
        // Call the inner handler.
        var response = await base.SendAsync(request, cancellationToken);
        Debug.WriteLine("Process response");
        return response;
    }
}

Observação

A chamada a base.SendAsync é assíncrona. Se o manipulador fizer algum trabalho após essa chamada, use a palavra-chave await, conforme mostrado.

Um manipulador de delegação também pode ignorar o manipulador interno e criar diretamente a resposta:

public class MessageHandler2 : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Create the response.
        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent("Hello!")
        };

        // Note: TaskCompletionSource creates a task that does not contain a delegate.
        var tsc = new TaskCompletionSource<HttpResponseMessage>();
        tsc.SetResult(response);   // Also sets the task state to "RanToCompletion"
        return tsc.Task;
    }
}

Se um manipulador de delegação criar a resposta sem chamar base.SendAsync, a solicitação ignorará o restante do pipeline. Isso pode ser útil para um manipulador que valida a solicitação (criando uma resposta de erro).

Diagrama de manipuladores de mensagens personalizados, ilustrando o processo para criar a resposta sem chamar o ponto base Send Async.

Adicionando um manipulador ao pipeline

Para adicionar um manipulador de mensagens no lado do servidor, adicione o manipulador à coleção HttpConfiguration.MessageHandlers . Se você usou o modelo "ASP.NET Aplicativo Web MVC 4" para criar o projeto, faça isso dentro da classe WebApiConfig :

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new MessageHandler1());
        config.MessageHandlers.Add(new MessageHandler2());

        // Other code not shown...
    }
}

Manipuladores de mensagens são chamados na mesma ordem em que aparecem na coleção MessageHandlers . Como eles estão aninhados, a mensagem de resposta viaja na outra direção. Ou seja, o último manipulador é o primeiro a receber a mensagem de resposta.

Observe que você não precisa definir os manipuladores internos; a estrutura da API Web conecta automaticamente os manipuladores de mensagens.

Se você estiver se auto-hospedando, crie uma instância da classe HttpSelfHostConfiguration e adicione os manipuladores à coleção MessageHandlers .

var config = new HttpSelfHostConfiguration("http://localhost");
config.MessageHandlers.Add(new MessageHandler1());
config.MessageHandlers.Add(new MessageHandler2());

Agora vamos examinar alguns exemplos de manipuladores de mensagens personalizados.

Exemplo: X-HTTP-Method-Override

X-HTTP-Method-Override é um cabeçalho HTTP não padrão. Ele foi projetado para clientes que não podem enviar determinados tipos de solicitação HTTP, como PUT ou DELETE. Em vez disso, o cliente envia uma solicitação POST e define o cabeçalho X-HTTP-Method-Override para o método desejado. Por exemplo:

X-HTTP-Method-Override: PUT

Aqui está um manipulador de mensagens que adiciona suporte para X-HTTP-Method-Override:

public class MethodOverrideHandler : DelegatingHandler      
{
    readonly string[] _methods = { "DELETE", "HEAD", "PUT" };
    const string _header = "X-HTTP-Method-Override";

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Check for HTTP POST with the X-HTTP-Method-Override header.
        if (request.Method == HttpMethod.Post && request.Headers.Contains(_header))
        {
            // Check if the header value is in our methods list.
            var method = request.Headers.GetValues(_header).FirstOrDefault();
            if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
            {
                // Change the request method.
                request.Method = new HttpMethod(method);
            }
        }
        return base.SendAsync(request, cancellationToken);
    }
}

No método SendAsync , o manipulador verifica se a mensagem de solicitação é uma solicitação POST e se ela contém o cabeçalho X-HTTP-Method-Override. Nesse caso, ele valida o valor do cabeçalho e modifica o método de solicitação. Por fim, o manipulador chama base.SendAsync para passar a mensagem para o próximo manipulador.

Quando a solicitação atingir a classe HttpControllerDispatcher , HttpControllerDispatcher roteará a solicitação com base no método de solicitação atualizado.

Exemplo: adicionando um cabeçalho de resposta personalizado

Aqui está um manipulador de mensagens que adiciona um cabeçalho personalizado a cada mensagem de resposta:

// .Net 4.5
public class CustomHeaderHandler : DelegatingHandler
{
    async protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
        response.Headers.Add("X-Custom-Header", "This is my custom header.");
        return response;
    }
}

Primeiro, o manipulador chama base.SendAsync para passar a solicitação para o manipulador de mensagens internas. O manipulador interno retorna uma mensagem de resposta, mas o faz de forma assíncrona usando um objeto Task<T> . A mensagem de resposta não estará disponível até base.SendAsync que seja concluída de forma assíncrona.

Este exemplo usa a palavra-chave await para executar o trabalho de forma assíncrona após SendAsync a conclusão. Se você estiver direcionando .NET Framework 4.0, use a Tarefa<T>. Método ContinueWith:

public class CustomHeaderHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken).ContinueWith(
            (task) =>
            {
                HttpResponseMessage response = task.Result;
                response.Headers.Add("X-Custom-Header", "This is my custom header.");
                return response;
            }
        );
    }
}

Exemplo: verificando se há uma chave de API

Alguns serviços Web exigem que os clientes incluam uma chave de API em sua solicitação. O exemplo a seguir mostra como um manipulador de mensagens pode marcar solicitações para uma chave de API válida:

public class ApiKeyHandler : DelegatingHandler
{
    public string Key { get; set; }

    public ApiKeyHandler(string key)
    {
        this.Key = key;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (!ValidateKey(request))
        {
            var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
            var tsc = new TaskCompletionSource<HttpResponseMessage>();
            tsc.SetResult(response);    
            return tsc.Task;
        }
        return base.SendAsync(request, cancellationToken);
    }

    private bool ValidateKey(HttpRequestMessage message)
    {
        var query = message.RequestUri.ParseQueryString();
        string key = query["key"];
        return (key == Key);
    }
}

Esse manipulador procura a chave de API na cadeia de caracteres de consulta URI. (Neste exemplo, presumimos que a chave seja uma cadeia de caracteres estática. Uma implementação real provavelmente usaria uma validação mais complexa.) Se a cadeia de caracteres de consulta contiver a chave, o manipulador passará a solicitação para o manipulador interno.

Se a solicitação não tiver uma chave válida, o manipulador criará uma mensagem de resposta com status 403, Proibido. Nesse caso, o manipulador não chama base.SendAsync, portanto, o manipulador interno nunca recebe a solicitação, nem o controlador. Portanto, o controlador pode assumir que todas as solicitações de entrada têm uma chave de API válida.

Observação

Se a chave de API se aplicar apenas a determinadas ações do controlador, considere usar um filtro de ação em vez de um manipulador de mensagens. Os filtros de ação são executados após a execução do roteamento de URI.

manipuladores de mensagens Per-Route

Os manipuladores na coleção HttpConfiguration.MessageHandlers se aplicam globalmente.

Como alternativa, você pode adicionar um manipulador de mensagens a uma rota específica ao definir a rota:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "Route1",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        config.Routes.MapHttpRoute(
            name: "Route2",
            routeTemplate: "api2/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional },
            constraints: null,
            handler: new MessageHandler2()  // per-route message handler
        );

        config.MessageHandlers.Add(new MessageHandler1());  // global message handler
    }
}

Neste exemplo, se o URI da solicitação corresponder a "Route2", a solicitação será expedida para MessageHandler2. O diagrama a seguir mostra o pipeline para essas duas rotas:

Diagrama de pipeline de manipuladores de mensagens por rota, ilustrando o processo para adicionar um manipulador de mensagens a uma rota específica definindo a rota.

Observe que MessageHandler2 substitui o HttpControllerDispatcher padrão. Neste exemplo, MessageHandler2 cria a resposta e as solicitações que correspondem a "Route2" nunca vão para um controlador. Isso permite substituir todo o mecanismo do controlador de API Web pelo seu próprio ponto de extremidade personalizado.

Como alternativa, um manipulador de mensagens por rota pode delegar para HttpControllerDispatcher, que, em seguida, despacha para um controlador.

Diagrama de pipeline de manipuladores de mensagens por rota, mostrando o processo para delegar ao Dispatcher do Controlador h t t p, que, em seguida, envia para um controlador.

O código a seguir mostra como configurar essa rota:

// List of delegating handlers.
DelegatingHandler[] handlers = new DelegatingHandler[] {
    new MessageHandler3()
};

// Create a message handler chain with an end-point.
var routeHandlers = HttpClientFactory.CreatePipeline(
    new HttpControllerDispatcher(config), handlers);

config.Routes.MapHttpRoute(
    name: "Route2",
    routeTemplate: "api2/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional },
    constraints: null,
    handler: routeHandlers
);