Freigeben über


HTTP-Nachrichtenhandler in ASP.NET Web-API

Ein Nachrichtenhandler ist eine Klasse, die eine HTTP-Anforderung empfängt und eine HTTP-Antwort zurückgibt. Nachrichtenhandler werden von der abstrakten HttpMessageHandler-Klasse abgeleitet.

In der Regel werden eine Reihe von Nachrichtenhandlern miteinander verkettet. Der erste Handler empfängt eine HTTP-Anforderung, führt eine Verarbeitung durch und gibt die Anforderung an den nächsten Handler weiter. Irgendwann wird die Antwort erstellt und geht zurück in die Kette. Dieses Muster wird als delegierender Handler bezeichnet.

Diagramm der miteinander verketteten Nachrichtenhandler, die den Prozess zum Empfangen einer H T T P-Anforderung veranschaulichen und eine H T T P-Antwort zurückgeben.

Server-Side Nachrichten-Handler

Auf der Serverseite verwendet die Web-API-Pipeline einige integrierte Nachrichtenhandler:

  • HttpServer ruft die Anforderung vom Host ab.
  • HttpRoutingDispatcher verteilt die Anforderung basierend auf der Route.
  • HttpControllerDispatcher sendet die Anforderung an einen Web-API-Controller.

Sie können der Pipeline benutzerdefinierte Handler hinzufügen. Nachrichten-Handler eignen sich gut für querschneidende Belange, die auf der Ebene von HTTP-Nachrichten statt auf Controlleraktionen arbeiten. Ein Nachrichtenhandler kann z. B. folgendes ausführen:

  • Lesen oder Ändern von Anforderungsheadern.
  • Fügen Sie Antworten einen Antwortheader hinzu.
  • Überprüfen Sie Anfragen, bevor sie den Controller erreichen.

Dieses Diagramm zeigt zwei benutzerdefinierte Handler, die in die Pipeline eingefügt wurden:

Diagramm serverseitiger Meldungshandler mit zwei benutzerdefinierten Handlern, die in die Web A P I-Pipeline eingefügt wurden.

Hinweis

Auf clientseitiger Seite verwendet HttpClient auch Nachrichtenhandler. Weitere Informationen finden Sie unter HttpClient Message Handlers.

Benutzerdefinierte Nachrichtenhandler

Um einen benutzerdefinierten Nachrichtenhandler zu schreiben, leiten Sie von System.Net.Http.DelegatingHandler ab und überschreiben Sie die SendAsync-Methode. Diese Methode hat die folgende Signatur:

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

Die Methode verwendet eine HttpRequestMessage als Eingabe und gibt asynchron eine HttpResponseMessage zurück. Eine typische Implementierung führt folgende Aktionen aus:

  1. Verarbeiten sie die Anforderungsnachricht.
  2. Rufen Sie base.SendAsync auf, um die Anforderung an den inneren Handler zu senden.
  3. Der innere Handler gibt eine Antwortnachricht zurück. (Dieser Schritt ist asynchron.)
  4. Verarbeiten Sie die Antwort, und geben Sie sie an den Aufrufer zurück.

Hier ist ein triviales Beispiel:

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

Hinweis

Der Aufruf an base.SendAsync ist asynchron. Wenn der Handler nach diesem Aufruf weitere Arbeiten durchführt, verwenden Sie das Await-Schlüsselwort, wie gezeigt.

Ein delegierender Handler kann auch den inneren Handler überspringen und die Antwort direkt erstellen:

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

Wenn ein delegierender Handler die Antwort ohne Aufruf base.SendAsyncerstellt, überspringt die Anforderung den Rest der Pipeline. Dies kann für einen Handler hilfreich sein, der die Anforderung überprüft (Erstellen einer Fehlerantwort).

Diagramm von benutzerdefinierten Nachrichtenhandlern, die den Prozess veranschaulichen, um die Antwort zu erstellen, ohne Basispunkt Send Async aufzurufen.

Hinzufügen eines Handlers zur Pipeline

Um einen Nachrichtenhandler auf serverseitiger Seite hinzuzufügen, fügen Sie den Handler zur HttpConfiguration.MessageHandlers-Auflistung hinzu. Wenn Sie die Vorlage "ASP.NET MVC 4-Webanwendung" zum Erstellen des Projekts verwendet haben, können Sie dies innerhalb der WebApiConfig-Klasse tun:

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

        // Other code not shown...
    }
}

Nachrichtenhandler werden in derselben Reihenfolge aufgerufen, in der sie in der MessageHandlers-Auflistung angezeigt werden. Da sie geschachtelt sind, wird die Antwortnachricht in die andere Richtung verschoben. Das heißt, der letzte Handler erhält als erster die Antwortnachricht.

Beachten Sie, dass Sie die inneren Handler nicht festlegen müssen. Das Web-API-Framework verbindet die Nachrichtenhandler automatisch.

Wenn Sie selbsthosten, erstellen Sie eine Instanz der HttpSelfHostConfiguration-Klasse , und fügen Sie die Handler zur MessageHandlers-Auflistung hinzu.

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

Sehen wir uns nun einige Beispiele für benutzerdefinierte Nachrichtenhandler an.

Beispiel: X-HTTP-Method-Override

X-HTTP-Method-Override ist ein nicht standardmäßiger HTTP-Header. Es wurde für Clients entwickelt, die bestimmte HTTP-Anforderungstypen, z. B. PUT oder DELETE, nicht senden können. Stattdessen sendet der Client eine POST-Anforderung und legt den X-HTTP-Method-Override-Header auf die gewünschte Methode fest. Beispiel:

X-HTTP-Method-Override: PUT

Hier ist ein Nachrichtenhandler, der Unterstützung für X-HTTP-Method-Override hinzufügt:

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

In der SendAsync-Methode überprüft der Handler, ob es sich bei der Anforderungsnachricht um eine POST-Anforderung handelt und ob sie den X-HTTP-Method-Override-Header enthält. Wenn dies der Fall ist, überprüft sie den Headerwert und ändert dann die Anforderungsmethode. Schließlich ruft der Handler die base.SendAsync Nachricht an den nächsten Handler weiter.

Wenn die Anforderung die HttpControllerDispatcher-Klasse erreicht, leitet HttpControllerDispatcher die Anforderung basierend auf der aktualisierten Anforderungsmethode weiter.

Beispiel: Hinzufügen eines benutzerdefinierten Antwortheaders

Hier ist ein Nachrichtenhandler, der jeder Antwortnachricht eine benutzerdefinierte Kopfzeile hinzufügt:

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

Zuerst ruft der Handler auf base.SendAsync , um die Anforderung an den inneren Nachrichtenhandler zu übergeben. Der innere Handler gibt eine Antwortnachricht zurück, aber er verwendet asynchron ein Task<T-Objekt> . Die Antwortnachricht ist erst verfügbar, wenn base.SendAsync asynchron abgeschlossen ist.

In diesem Beispiel wird das Schlüsselwort 'await' verwendet, um nach dem Abschluss von SendAsync asynchron arbeiten zu können. Wenn Sie auf .NET Framework 4.0 abzielen, verwenden Sie die Task<T>.ContinueWith-Methode:

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

Beispiel: Überprüfen auf einen API-Schlüssel

Einige Webdienste erfordern, dass Clients einen API-Schlüssel in ihre Anforderung einschließen. Das folgende Beispiel zeigt, wie ein Nachrichtenhandler Anforderungen für einen gültigen API-Schlüssel überprüfen kann:

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

Dieser Handler sucht nach dem API-Schlüssel in der URI-Abfragezeichenfolge. (In diesem Beispiel wird davon ausgegangen, dass der Schlüssel eine statische Zeichenfolge ist. Eine echte Implementierung würde wahrscheinlich komplexere Validierung verwenden.) Wenn die Abfragezeichenfolge den Schlüssel enthält, übergibt der Handler die Anforderung an den inneren Handler.

Wenn die Anforderung keinen gültigen Schlüssel aufweist, erstellt der Handler eine Antwortnachricht mit dem Status 403, Verboten. In diesem Fall ruft der Handler base.SendAsync nicht auf, sodass der innere Handler die Anforderung nicht empfängt, noch der Controller. Daher kann der Controller davon ausgehen, dass alle eingehenden Anforderungen über einen gültigen API-Schlüssel verfügen.

Hinweis

Wenn der API-Schlüssel nur für bestimmte Controlleraktionen gilt, sollten Sie einen Aktionsfilter anstelle eines Nachrichtenhandlers verwenden. Aktionsfilter werden nach der Ausführung des URI-Routings ausgeführt.

Per-Route-Nachrichten-Handler

Handler in der HttpConfiguration.MessageHandlers-Auflistung gelten global.

Alternativ können Sie einen Nachrichtenhandler zu einer bestimmten Route hinzufügen, wenn Sie die Route definieren:

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

Wenn der Anforderungs-URI in diesem Beispiel mit "Route2" übereinstimmt, wird die Anforderung an MessageHandler2. Das folgende Diagramm zeigt die Pipeline für diese beiden Routen:

Diagramm der Nachrichtenhandler-Pipeline pro Route, das den Prozess veranschaulicht, wie ein Nachrichtenhandler zu einer bestimmten Route hinzugefügt wird, indem die Route definiert wird.

Beachten Sie, dass MessageHandler2 den standardmäßigen HttpControllerDispatcher ersetzt. In diesem Beispiel wird MessageHandler2 die Antwort erstellt, und Anforderungen, die mit "Route2" übereinstimmen, werden nie an einen Controller geleitet. Auf diese Weise können Sie den gesamten Web-API-Controllermechanismus durch Ihren eigenen benutzerdefinierten Endpunkt ersetzen.

Alternativ kann ein Nachrichtenhandler pro Route an HttpControllerDispatcher delegiert werden, der dann an einen Controller verteilt wird.

Diagramm der Nachrichtenhandler-Pipeline pro Route, das den Prozess des Delegierens an den h t t p-Controller-Dispatcher zeigt, der dann an einen Controller delegiert.

Der folgende Code zeigt, wie Sie diese Route konfigurieren:

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