共用方式為


ASP.NET Web API 中的 HTTP 訊息處理常式

訊息處理常式是接收 HTTP 要求的類別,並傳回 HTTP 回應。 訊息處理常式衍生自抽象 HttpMessageHandler 類別。

一般而言,一系列的訊息處理常式會鏈結在一起。 第一個處理常式會接收 HTTP 要求、執行一些處理,並將要求提供給下一個處理常式。 在某些時候,會建立回應並備份鏈結。 此模式稱為委派處理常式。

鏈結在一起的訊息處理常式圖表,說明接收 H T T P 要求並傳回 H T T P 回應的流程。

伺服器端訊息處理常式

在伺服器端,Web API 管線會使用一些內建訊息處理常式:

  • HttpServer 從主機取得要求。
  • HttpRoutingDispatcher 會根據路由分派要求。
  • HttpControllerDispatcher 會將要求傳送至 Web API 控制器。

您可以將自訂處理常式新增至管線。 訊息處理常式適用於在 HTTP 訊息層級運作的跨領域關注 (而不是控制器動作)。 例如,訊息處理常式可能會:

  • 讀取或修改要求標頭。
  • 將回應標頭新增至回應。
  • 在要求到達控制器之前先驗證要求。

此圖顯示插入管線的自訂處理常式:

伺服器端訊息處理常式的圖表,其中顯示插入 Web A P I 管線的兩個自訂處理常式。

注意

在用戶端上,HttpClient 也會使用訊息處理常式。 有關詳細資訊,請參閱 HttpClient 訊息處理常式

自訂訊息處理常式

若要撰寫自訂訊息處理常式,請衍生自 System.Net.Http.DelegatingHandler 並覆寫 SendAsync 方法。 此方法具有下列簽章:

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

方法會採用 HttpRequestMessage 作為輸入,並以非同步方式傳回 HttpResponseMessage。 一般實作會執行下列操作:

  1. 處理要求訊息。
  2. 呼叫 base.SendAsync 以將要求傳送至內部處理常式。
  3. 內部處理常式會傳回回應訊息。 (此步驟是非同步的。)
  4. 處理回應並將它傳回給呼叫端。

以下是一個簡單的範例:

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

注意

base.SendAsync 的呼叫是非同步的。 如果處理常式在此呼叫之後執行任何工作,請使用 await 關鍵字,如下所示。

委派處理常式也可以略過內部處理常式,並直接建立回應:

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

如果委派處理常式在沒有呼叫 base.SendAsync 的情況下建立回應,要求會略過管線的其餘部分。 這對於驗證要求的處理常式很實用 (建立錯誤回應)。

自訂訊息處理常式的圖表,說明了在不呼叫基點 Send Async 的情況下建立回應的流程。

將處理常式新增至管線

若要在伺服器端新增訊息處理常式,請將處理常式新增至 HttpConfiguration.MessageHandlers 集合。 如果您使用「ASP.NET MVC 4 Web 應用程式」範本來建立專案,您可以在 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...
    }
}

訊息處理常式的呼叫順序與 MessageHandlers 集合中出現的順序相同。 因為它們是巢狀的,所以回應訊息會以另一個方向移動。 也就是說,最後一個處理常式是第一個取得回應訊息的處理常式。

請注意,您不需要設定內部處理常式;Web API 架構會自動連接訊息處理常式。

如果您是自我裝載,請建立 HttpSelfHostConfiguration 類別的執行個體並將處理常式新增至 MessageHandlers 集合中。

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

現在讓我們看看自訂訊息處理常式的一些範例。

範例:X-HTTP-Method-Override

X-HTTP-Method-Override 是非標準 HTTP 標頭。 它是針對無法傳送特定 HTTP 要求類型的用戶端所設計,例如 PUT 或 DELETE。 相反地,用戶端會傳送 POST 要求,並將 X-HTTP-Method-Override 標頭設定為所需的方法。 例如:

X-HTTP-Method-Override: PUT

以下是新增 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);
    }
}

SendAsync 方法中,處理常式會檢查要求訊息是否為 POST 要求,以及它是否包含 X-HTTP-Method-Override 標頭。 如果是,它會驗證標頭值,然後修改要求方法。 最後,處理常式會呼叫 base.SendAsync,將訊息傳遞至下一個處理常式。

當要求到達 HttpControllerDispatcher 類別時,HttpControllerDispatcher 將根據更新後的要求方法來路由要求。

範例:新增自訂回應標頭

以下是將自訂標頭新增至每個回應訊息的訊息處理常式:

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

首先,處理常式會呼叫 base.SendAsync,以將要求傳遞至內部訊息處理常式。 內部處理常式會傳回回應訊息,但會使用 Task<T> 物件以非同步方式執行此動作。 在 base.SendAsync 非同步完成之前,回應訊息無法使用。

此範例使用 await 關鍵字在 SendAsync 完成後非同步執行工作。 如果您的目標是 .NET Framework 4.0,請使用 Task<T>.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;
            }
        );
    }
}

範例:檢查 API 金鑰

有些 Web 服務會要求用戶端在其要求中包含 API 金鑰。 下列範例示範訊息處理常式如何檢查有效 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);
    }
}

此處理常式會在 URI 查詢字串中尋找 API 金鑰。 (在此範例中,我們假設金鑰是靜態字串。實際的實作可能會使用更複雜的驗證。如果查詢字串包含金鑰,處理常式會將要求傳遞至內部處理常式。

如果要求沒有有效的金鑰,處理常式會建立狀態為 403 禁止的回應訊息。 在此情況下,處理常式不會呼叫 base.SendAsync,因此內部處理常式永遠不會收到要求,也不會收到控制器。 因此,控制器可以假設所有傳入要求都有有效的 API 金鑰。

注意

如果 API 金鑰僅適用於特定控制器動作,請考慮使用動作篩選,而不是訊息處理常式。 執行 URI 路由之後執行的動作篩選條件。

個別路由訊息處理常式

HttpConfiguration.MessageHandlers 集合中的處理常式全域適用。

或者,當您定義路由時,您可以將訊息處理常式新增至特定路由:

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

在此範例中,如果要求 URI 符合「Route2」,則該要求將分派到 MessageHandler2。 下圖顯示這兩個路由的管線:

每個路由訊息處理常式管線的圖表,說明透過定義路由將訊息處理常式新增至特定路由的流程。

請注意,MessageHandler2 取代了預設的 HttpControllerDispatcher。 在此範例中,MessageHandler2 建立回應,並要求符合「Route2」永遠不會移至控制器。 這可讓您將整個 Web API 控制器機制取代為您自己的自訂端點。

或者,個別路由訊息處理常式可以委派給 HttpControllerDispatcher,然後分派至控制器。

個別路由訊息處理常式管線的圖表,其中顯示委派給 h t t p 控制器調度員的流程,然後分派至控制器。

下列程式碼會示範如何設定此路由:

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