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 übergibt die Anforderung an den nächsten Handler. Irgendwann wird die Antwort erstellt und in der Kette wieder nach oben geschaltet. Dieses Muster wird als delegierender Handler bezeichnet.

Diagramm der miteinander verketteten Nachrichtenhandler, der den Prozess veranschaulicht, um eine H T T P-Anforderung zu empfangen und eine H T T P-Antwort zurückzugeben.

Server-Side-Nachrichtenhandler

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

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

Sie können der Pipeline benutzerdefinierte Handler hinzufügen. Nachrichtenhandler eignen sich gut für Querschnittsaspekte, die auf der Ebene von HTTP-Nachrichten (anstelle von Controlleraktionen) ausgeführt werden. Ein Nachrichtenhandler kann z. B.:

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

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

Diagramm der serverseitigen Nachrichtenhandler mit zwei benutzerdefinierten Handlern, die in die Web-AP I-Pipeline eingefügt wurden.

Hinweis

Auf der Clientseite verwendet HttpClient auch Nachrichtenhandler. Weitere Informationen finden Sie unter HttpClient-Nachrichtenhandler.

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 folgendes 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 einfaches 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 von base.SendAsync ist asynchron. Wenn der Handler nach diesem Aufruf eine Arbeit ausführt, verwenden Sie wie gezeigt den await-Schlüsselwort (keyword).

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 erstellt, ohne aufzurufen base.SendAsync, überspringt die Anforderung den Rest der Pipeline. Dies kann für einen Handler nützlich sein, der die Anforderung überprüft (erstellen einer Fehlerantwort).

Diagramm der benutzerdefinierten Nachrichtenhandler, die den Prozess zum Erstellen der Antwort veranschaulichen, ohne den Basispunkt Send Async aufzurufen.

Hinzufügen eines Handlers zur Pipeline

Um serverseitig einen Nachrichtenhandler hinzuzufügen, fügen Sie den Handler der HttpConfiguration.MessageHandlers-Auflistung hinzu. Wenn Sie zum Erstellen des Projekts die Vorlage "ASP.NET MVC 4-Webanwendung" verwendet haben, können Sie dies in 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 ist der erste, der die Antwortnachricht erhält.

Beachten Sie, dass Sie die inneren Handler nicht festlegen müssen. Das Web-API-Framework stellt automatisch eine Verbindung mit den Nachrichtenhandlern her.

Wenn Sie sich selbst hosten, erstellen Sie eine instance der HttpSelfHostConfiguration-Klasse, und fügen Sie die Handler der 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 ist für Clients konzipiert, die bestimmte HTTP-Anforderungstypen wie 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. Zum Beispiel:

X-HTTP-Method-Override: PUT

Hier sehen Sie einen 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 die Anforderungsnachricht eine POST-Anforderung ist und ob sie den X-HTTP-Method-Override-Header enthält. Wenn ja, wird der Headerwert überprüft und dann die Anforderungsmethode geändert. Schließlich ruft der Handler auf base.SendAsync , um die Nachricht an den nächsten Handler zu übergeben.

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

Beispiel: Hinzufügen eines benutzerdefinierten Antwortheaders

Hier sehen Sie einen Nachrichtenhandler, der jeder Antwortnachricht einen benutzerdefinierten Header 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 tut dies asynchron mithilfe eines Task T-Objekts<>. Die Antwortnachricht ist erst verfügbar, wenn base.SendAsync sie asynchron abgeschlossen wird.

In diesem Beispiel wird die await-Schlüsselwort (keyword) verwendet, um nach SendAsync Abschluss der Arbeit asynchron auszuführen. Wenn Sie .NET Framework 4.0 als Ziel verwenden, verwenden Sie die Aufgabe<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: Suchen nach einem 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 auf 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 in der URI-Abfragezeichenfolge nach dem API-Schlüssel. (In diesem Beispiel wird davon ausgegangen, dass der Schlüssel eine statische Zeichenfolge ist. Eine echte Implementierung würde wahrscheinlich eine 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 status 403, Verboten. In diesem Fall ruft der Handler nicht auf base.SendAsync, sodass der innere Handler weder die Anforderung 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 anstelle eines Nachrichtenhandlers einen Aktionsfilter verwenden. Aktionsfilter werden nach dem URI-Routing ausgeführt.

Per-Route-Nachrichtenhandler

Handler in der HttpConfiguration.MessageHandlers-Auflistung gelten global.

Alternativ können Sie einer bestimmten Route einen Nachrichtenhandler 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 in diesem Beispiel der Anforderungs-URI mit "Route2" übereinstimmt, wird die Anforderung an MessageHandler2weitergeleitet. Das folgende Diagramm zeigt die Pipeline für diese beiden Routen:

Diagramm der Pipeline von Nachrichtenhandlern pro Route, die den Prozess zum Hinzufügen eines Nachrichtenhandlers zu einer bestimmten Route durch Definieren der Route veranschaulicht.

Beachten Sie, dass MessageHandler2 den Standard httpControllerDispatcher ersetzt. In diesem Beispiel MessageHandler2 wird die Antwort erstellt, und Anforderungen, die mit "Route2" übereinstimmen, werden nie an einen Controller gesendet. Dadurch können Sie den gesamten Web-API-Controllermechanismus durch Ihren eigenen benutzerdefinierten Endpunkt ersetzen.

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

Diagramm der Pipeline von Nachrichtenhandlern pro Route, das den Prozess zum Delegieren an h t t p Controller Dispatcher zeigt, der dann an einen Controller verteilt wird.

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