Obslužné rutiny zpráv HTTP ve webovém rozhraní API ASP.NET

Obslužná rutina zprávy je třída, která přijímá požadavek HTTP a vrací odpověď HTTP. Obslužné rutiny zprávy jsou odvozeny z abstraktní Třídy HttpMessageHandler .

Obvykle je řada obslužných rutin zpráv zřetězených dohromady. První obslužná rutina přijme požadavek HTTP, provede určité zpracování a předá požadavek další obslužné rutině. V určitém okamžiku se vytvoří odpověď a vrátí se zpět do řetězu. Tento vzor se nazývá obslužná rutina delegování .

Diagram zřetězených obslužných rutin zpráv znázorňující proces přijetí požadavku H T T P a vrácení odpovědi H T T P

Server-Side obslužné rutiny zpráv

Kanál webového rozhraní API na straně serveru používá některé integrované obslužné rutiny zpráv:

  • HttpServer získá požadavek z hostitele.
  • HttpRoutingDispatcher odešle požadavek na základě trasy.
  • HttpControllerDispatcher odešle požadavek do kontroleru webového rozhraní API.

Do kanálu můžete přidat vlastní obslužné rutiny. Obslužné rutiny zpráv jsou vhodné pro průřezové záležitosti, které pracují na úrovni zpráv HTTP (místo akcí kontroleru). Obslužná rutina zprávy může například:

  • Čtení nebo úprava hlaviček požadavků
  • Přidejte do odpovědí hlavičku odpovědi.
  • Ověřte požadavky předtím, než se dostanou k kontroleru.

Tento diagram znázorňuje dva vlastní obslužné rutiny vložené do kanálu:

Diagram obslužných rutin zpráv na straně serveru zobrazující dva vlastní obslužné rutiny vložené do kanálu Web A P I

Poznámka

Na straně klienta používá HttpClient také obslužné rutiny zpráv. Další informace najdete v tématu Obslužné rutiny zpráv HttpClient.

Vlastní obslužné rutiny zpráv

Chcete-li napsat vlastní obslužnou rutinu zprávy, odvodit z System.Net.Http.DelegatingHandler a přepsat Metodu SendAsync . Tato metoda má následující podpis:

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

Metoda přijímá HttpRequestMessage jako vstup a asynchronně vrací HttpResponseMessage. Typická implementace provede následující:

  1. Zpracujte zprávu požadavku.
  2. Voláním base.SendAsync odešlete požadavek do vnitřní obslužné rutiny.
  3. Vnitřní obslužná rutina vrátí zprávu odpovědi. (Tento krok je asynchronní.)
  4. Zpracujte odpověď a vraťte ji volajícímu.

Tady je triviální příklad:

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

Poznámka

Volání base.SendAsync je asynchronní. Pokud obslužná rutina po tomto volání provede nějakou práci, použijte klíčové slovo await , jak je znázorněno na obrázku.

Delegující obslužná rutina může také přeskočit vnitřní obslužnou rutinu a přímo vytvořit odpověď:

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

Pokud delegující obslužná rutina vytvoří odpověď bez volání base.SendAsync, požadavek přeskočí zbytek kanálu. To může být užitečné pro obslužnou rutinu, která ověřuje požadavek (vytváří chybovou odpověď).

Diagram vlastních obslužných rutin zpráv znázorňující proces vytvoření odpovědi bez volání základní tečky Send Async

Přidání obslužné rutiny do kanálu

Chcete-li přidat obslužnou rutinu zprávy na straně serveru, přidejte obslužnou rutinu do kolekce HttpConfiguration.MessageHandlers . Pokud jste k vytvoření projektu použili šablonu "webová aplikace ASP.NET MVC 4", můžete to udělat ve třídě 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...
    }
}

Obslužné rutiny zpráv jsou volány ve stejném pořadí, v jakém se zobrazují v kolekci MessageHandlers . Vzhledem k tomu, že jsou vnořené, zpráva odpovědi se pohybuje v opačném směru. To znamená, že poslední obslužná rutina je první, která získá zprávu odpovědi.

Všimněte si, že nemusíte nastavovat vnitřní obslužné rutiny. Rozhraní webového rozhraní API automaticky připojí obslužné rutiny zpráv.

Pokud jste self-hosting, vytvořte instanci HttpSelfHostConfiguration třídy a přidejte obslužné rutiny do messageHandlers kolekce.

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

Teď se podíváme na některé příklady vlastních obslužných rutin zpráv.

Příklad: X-HTTP-Method-Override

X-HTTP-Method-Override je nestandardní hlavička HTTP. Je určen pro klienty, kteří nemohou odesílat určité typy požadavků HTTP, například PUT nebo DELETE. Místo toho klient odešle požadavek POST a nastaví hlavičku X-HTTP-Method-Override na požadovanou metodu. Příklad:

X-HTTP-Method-Override: PUT

Tady je obslužná rutina zprávy, která přidává podporu pro 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);
    }
}

V metodě SendAsync obslužná rutina zkontroluje, jestli je zpráva požadavku POST a jestli obsahuje hlavičku X-HTTP-Method-Override. Pokud ano, ověří hodnotu hlavičky a pak upraví metodu požadavku. Nakonec obslužná rutina zavolá base.SendAsync , aby zprávu předala další obslužné rutině.

Když požadavek dosáhne třídy HttpControllerDispatcher , HttpControllerDispatcher směruje požadavek na základě aktualizované metody požadavku.

Příklad: Přidání vlastní hlavičky odpovědi

Tady je obslužná rutina zprávy, která do každé zprávy odpovědi přidá vlastní hlavičku:

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

Nejprve obslužná rutina volá base.SendAsync , aby předala požadavek do vnitřní obslužné rutiny zprávy. Vnitřní obslužná rutina vrátí zprávu odpovědi, ale provede to asynchronně pomocí objektu Task<T> . Zpráva odpovědi není k dispozici, dokud base.SendAsync se nedokon čte asynchronně.

Tento příklad používá klíčové slovo await k provádění práce asynchronně po SendAsync dokončení. Pokud cílíte na rozhraní .NET Framework 4.0, použijte úlohu<T>. Metoda 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;
            }
        );
    }
}

Příklad: Kontrola klíče rozhraní API

Některé webové služby vyžadují, aby klienti do své žádosti zahrnuli klíč rozhraní API. Následující příklad ukazuje, jak může obslužná rutina zprávy zkontrolovat požadavky na platný klíč rozhraní API:

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

Tato obslužná rutina vyhledá klíč rozhraní API v řetězci dotazu URI. (V tomto příkladu předpokládáme, že klíč je statický řetězec. Skutečná implementace by pravděpodobně používala složitější ověřování.) Pokud řetězec dotazu obsahuje klíč, obslužná rutina předá požadavek vnitřní obslužné rutině.

Pokud požadavek nemá platný klíč, obslužná rutina vytvoří zprávu odpovědi se stavem 403 Zakázáno. V tomto případě obslužná rutina nevolá base.SendAsync, takže vnitřní obslužná rutina nikdy neobdrží požadavek ani kontroler. Kontroler proto může předpokládat, že všechny příchozí požadavky mají platný klíč rozhraní API.

Poznámka

Pokud se klíč rozhraní API vztahuje pouze na určité akce kontroleru, zvažte použití filtru akcí místo obslužné rutiny zprávy. Po provedení směrování identifikátoru URI se spouštějí filtry akcí.

Per-Route obslužné rutiny zpráv

Obslužné rutiny v kolekci HttpConfiguration.MessageHandlers platí globálně.

Případně můžete přidat obslužnou rutinu zprávy ke konkrétní trase při definování trasy:

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

Pokud v tomto příkladu identifikátor URI požadavku odpovídá route2, odešle se požadavek do MessageHandler2. Následující diagram znázorňuje kanál pro tyto dvě trasy:

Diagram kanálu obslužných rutin zpráv na trasu znázorňující proces přidání obslužné rutiny zprávy do konkrétní trasy definováním trasy

Všimněte si, že MessageHandler2 nahrazuje výchozí HttpControllerDispatcher. V tomto příkladu vytvoří odpověď a požadavky, MessageHandler2 které odpovídají route2, nikdy nepřejdou na kontroler. To vám umožní nahradit celý mechanismus kontroleru webového rozhraní API vlastním koncovým bodem.

Případně obslužná rutina zprávy pro jednotlivé trasy může delegovat na HttpControllerDispatcher, který pak odešle do kontroleru.

Diagram kanálu obslužných rutin zpráv pro jednotlivé trasy zobrazující proces delegování na dispečera kontroleru, který pak odešle do kontroleru.

Následující kód ukazuje, jak nakonfigurovat tuto trasu:

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