Controladores de mensajes HTTP en ASP.NET Web APIHTTP Message Handlers in ASP.NET Web API

por Mike Wassonby Mike Wasson

Un controlador de mensajes es una clase que recibe una solicitud HTTP y devuelve una respuesta http.A message handler is a class that receives an HTTP request and returns an HTTP response. Los controladores de mensajes derivan de la clase HttpMessageHandler abstracta.Message handlers derive from the abstract HttpMessageHandler class.

Normalmente, una serie de controladores de mensajes se encadenan entre sí.Typically, a series of message handlers are chained together. El primer controlador recibe una solicitud HTTP, realiza algún procesamiento y proporciona la solicitud al siguiente controlador.The first handler receives an HTTP request, does some processing, and gives the request to the next handler. En algún momento, se crea la respuesta y se realiza una copia de seguridad de la cadena.At some point, the response is created and goes back up the chain. Este patrón se denomina controlador de delegación .This pattern is called a delegating handler.

Controladores de mensajes del servidorServer-Side Message Handlers

En el lado del servidor, la canalización de la API Web usa algunos controladores de mensajes integrados:On the server side, the Web API pipeline uses some built-in message handlers:

  • HttpServer obtiene la solicitud del host.HttpServer gets the request from the host.
  • HttpRoutingDispatcher envía la solicitud en función de la ruta.HttpRoutingDispatcher dispatches the request based on the route.
  • HttpControllerDispatcher envía la solicitud a un controlador de API Web.HttpControllerDispatcher sends the request to a Web API controller.

Puede agregar controladores personalizados a la canalización.You can add custom handlers to the pipeline. Los controladores de mensajes son buenos para cuestiones transversales que operan en el nivel de mensajes HTTP (en lugar de acciones de controlador).Message handlers are good for cross-cutting concerns that operate at the level of HTTP messages (rather than controller actions). Por ejemplo, un controlador de mensajes podría:For example, a message handler might:

  • Leer o modificar los encabezados de solicitud.Read or modify request headers.
  • Agregue un encabezado de respuesta a las respuestas.Add a response header to responses.
  • Valide las solicitudes antes de que lleguen al controlador.Validate requests before they reach the controller.

En este diagrama se muestran dos controladores personalizados insertados en la canalización:This diagram shows two custom handlers inserted into the pipeline:

Note

En el lado del cliente, HttpClient también usa controladores de mensajes.On the client side, HttpClient also uses message handlers. Para obtener más información, consulte controladores de mensajes HttpClient.For more information, see HttpClient Message Handlers.

Controladores de mensajes personalizadosCustom Message Handlers

Para escribir un controlador de mensajes personalizado, derive de System .net. http. DelegatingHandler e invalide el método SendAsync .To write a custom message handler, derive from System.Net.Http.DelegatingHandler and override the SendAsync method. Este método tiene la siguiente firma:This method has the following signature:

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

El método toma un HttpRequestMessage como entrada y devuelve de forma asincrónica un HttpResponseMessage.The method takes an HttpRequestMessage as input and asynchronously returns an HttpResponseMessage. Una implementación típica hace lo siguiente:A typical implementation does the following:

  1. Procesar el mensaje de solicitud.Process the request message.
  2. Llame a base.SendAsync para enviar la solicitud al controlador interno.Call base.SendAsync to send the request to the inner handler.
  3. El controlador interno devuelve un mensaje de respuesta.The inner handler returns a response message. (Este paso es asincrónico).(This step is asynchronous.)
  4. Procesa la respuesta y la devuelve al autor de la llamada.Process the response and return it to the caller.

Este es un ejemplo trivial:Here is a trivial example:

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;
    }
}

Note

La llamada a base.SendAsync es asincrónica.The call to base.SendAsync is asynchronous. Si el controlador realiza cualquier trabajo después de esta llamada, use la palabra clave Await , como se muestra.If the handler does any work after this call, use the await keyword, as shown.

Un controlador de delegación también puede omitir el controlador interno y crear directamente la respuesta:A delegating handler can also skip the inner handler and directly create the response:

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;
    }
}

Si un controlador de delegación crea la respuesta sin llamar a base.SendAsync, la solicitud omite el resto de la canalización.If a delegating handler creates the response without calling base.SendAsync, the request skips the rest of the pipeline. Esto puede ser útil para un controlador que valida la solicitud (creando una respuesta de error).This can be useful for a handler that validates the request (creating an error response).

Agregar un controlador a la canalizaciónAdding a Handler to the Pipeline

Para agregar un controlador de mensajes en el lado servidor, agregue el controlador a la colección HttpConfiguration. MessageHandlers .To add a message handler on the server side, add the handler to the HttpConfiguration.MessageHandlers collection. Si usó la plantilla "aplicación Web de ASP.NET MVC 4" para crear el proyecto, puede hacerlo dentro de la clase WebApiConfig :If you used the "ASP.NET MVC 4 Web Application" template to create the project, you can do this inside the WebApiConfig class:

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

        // Other code not shown...
    }
}

Se llama a los controladores de mensajes en el mismo orden en que aparecen en la colección MessageHandlers .Message handlers are called in the same order that they appear in MessageHandlers collection. Dado que están anidados, el mensaje de respuesta se desplaza en la otra dirección.Because they are nested, the response message travels in the other direction. Es decir, el último controlador es el primero en obtener el mensaje de respuesta.That is, the last handler is the first to get the response message.

Tenga en cuenta que no es necesario establecer los controladores internos; el marco de trabajo de la API Web conecta automáticamente los controladores de mensajes.Notice that you don't need to set the inner handlers; the Web API framework automatically connects the message handlers.

Si está autohospedado, cree una instancia de la clase HttpSelfHostConfiguration y agregue los controladores a la colección MessageHandlers .If you are self-hosting, create an instance of the HttpSelfHostConfiguration class and add the handlers to the MessageHandlers collection.

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

Ahora veamos algunos ejemplos de controladores de mensajes personalizados.Now let's look at some examples of custom message handlers.

Ejemplo: X-HTTP-Method-overrideExample: X-HTTP-Method-Override

X-HTTP-Method-override es un encabezado HTTP no estándar.X-HTTP-Method-Override is a non-standard HTTP header. Está diseñado para los clientes que no pueden enviar determinados tipos de solicitud HTTP, como PUT o DELETE.It is designed for clients that cannot send certain HTTP request types, such as PUT or DELETE. En su lugar, el cliente envía una solicitud POST y establece el encabezado X-HTTP-Method-override en el método deseado.Instead, the client sends a POST request and sets the X-HTTP-Method-Override header to the desired method. Por ejemplo:For example:

X-HTTP-Method-Override: PUT

Este es un controlador de mensajes que agrega compatibilidad con X-HTTP-Method-override:Here is a message handler that adds support for 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);
    }
}

En el método SendAsync , el controlador comprueba si el mensaje de solicitud es una solicitud post y si contiene el encabezado X-http-Method-override.In the SendAsync method, the handler checks whether the request message is a POST request, and whether it contains the X-HTTP-Method-Override header. Si es así, valida el valor de encabezado y, a continuación, modifica el método de solicitud.If so, it validates the header value, and then modifies the request method. Por último, el controlador llama a base.SendAsync para pasar el mensaje al siguiente controlador.Finally, the handler calls base.SendAsync to pass the message to the next handler.

Cuando la solicitud llega a la clase HttpControllerDispatcher , HttpControllerDispatcher enrutará la solicitud según el método de solicitud actualizado.When the request reaches the HttpControllerDispatcher class, HttpControllerDispatcher will route the request based on the updated request method.

Ejemplo: agregar un encabezado de respuesta personalizadoExample: Adding a Custom Response Header

Este es un controlador de mensajes que agrega un encabezado personalizado a cada mensaje de respuesta:Here is a message handler that adds a custom header to every response message:

// .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;
    }
}

En primer lugar, el controlador llama a base.SendAsync para pasar la solicitud al controlador de mensajes interno.First, the handler calls base.SendAsync to pass the request to the inner message handler. El controlador interno devuelve un mensaje de respuesta, pero lo hace de forma asincrónica mediante una tarea<t> objeto.The inner handler returns a response message, but it does so asynchronously using a Task<T> object. El mensaje de respuesta no está disponible hasta que base.SendAsync se completa de forma asincrónica.The response message is not available until base.SendAsync completes asynchronously.

En este ejemplo se usa la palabra clave Await para realizar el trabajo de forma asincrónica después de que se complete SendAsync.This example uses the await keyword to perform work asynchronously after SendAsync completes. Si tiene como destino .NET Framework 4,0, use la tarea<t> . Método ContinueWith :If you are targeting .NET Framework 4.0, use the Task<T>.ContinueWith method:

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;
            }
        );
    }
}

Ejemplo: comprobar una clave de APIExample: Checking for an API Key

Algunos servicios Web requieren que los clientes incluyan una clave de API en su solicitud.Some web services require clients to include an API key in their request. En el ejemplo siguiente se muestra cómo un controlador de mensajes puede comprobar las solicitudes de una clave de API válida:The following example shows how a message handler can check requests for a valid API key:

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);
    }
}

Este controlador busca la clave de API en la cadena de consulta de URI.This handler looks for the API key in the URI query string. (En este ejemplo, se supone que la clave es una cadena estática.(For this example, we assume that the key is a static string. Una implementación real probablemente utilizará una validación más compleja). Si la cadena de consulta contiene la clave, el controlador pasa la solicitud al controlador interno.A real implementation would probably use more complex validation.) If the query string contains the key, the handler passes the request to the inner handler.

Si la solicitud no tiene una clave válida, el controlador crea un mensaje de respuesta con el estado 403, prohibido.If the request does not have a valid key, the handler creates a response message with status 403, Forbidden. En este caso, el controlador no llama a base.SendAsync, por lo que el controlador interno nunca recibe la solicitud ni el controlador.In this case, the handler does not call base.SendAsync, so the inner handler never receives the request, nor does the controller. Por lo tanto, el controlador puede suponer que todas las solicitudes entrantes tienen una clave de API válida.Therefore, the controller can assume that all incoming requests have a valid API key.

Note

Si la clave de API solo se aplica a determinadas acciones del controlador, considere la posibilidad de usar un filtro de acción en lugar de un controlador de mensajes.If the API key applies only to certain controller actions, consider using an action filter instead of a message handler. Los filtros de acción se ejecutan después de realizar el enrutamiento de URI.Action filters run after URI routing is performed.

Controladores de mensajes por rutaPer-Route Message Handlers

Los controladores de la colección HttpConfiguration. MessageHandlers se aplican globalmente.Handlers in the HttpConfiguration.MessageHandlers collection apply globally.

Como alternativa, puede Agregar un controlador de mensajes a una ruta específica al definir la ruta:Alternatively, you can add a message handler to a specific route when you define the route:

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
    }
}

En este ejemplo, si el URI de solicitud coincide con "Route2", la solicitud se envía a MessageHandler2.In this example, if the request URI matches "Route2", the request is dispatched to MessageHandler2. En el diagrama siguiente se muestra la canalización para estas dos rutas:The following diagram shows the pipeline for these two routes:

Observe que MessageHandler2 reemplaza el valor predeterminado de HttpControllerDispatcher.Notice that MessageHandler2 replaces the default HttpControllerDispatcher. En este ejemplo, MessageHandler2 crea la respuesta y las solicitudes que coinciden con "Route2" nunca van a un controlador.In this example, MessageHandler2 creates the response, and requests that match "Route2" never go to a controller. Esto le permite reemplazar todo el mecanismo del controlador de API Web por su propio punto de conexión personalizado.This lets you replace the entire Web API controller mechanism with your own custom endpoint.

Como alternativa, un controlador de mensajes por ruta puede delegar a HttpControllerDispatcher, que después envía a un controlador.Alternatively, a per-route message handler can delegate to HttpControllerDispatcher, which then dispatches to a controller.

En el código siguiente se muestra cómo configurar esta ruta:The following code shows how to configure this route:

// 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
);