ASP.NET Web API實踐系列05,訊息處理管道

Darren Ji發表於2014-10-28

ASP.NET Web API的訊息處理管道可以理解為請求到達Controller之前、Controller返回響應之後的處理機制。之所以需要了解訊息處理管道,是因為我們可以藉助它來實現對請求和響應的自定義處理。所有的請求被封裝到HttpRequestMessage這個類中,所有的響應被封裝到HttpResponseMessage這個類中。

 

既然訊息處理管道是可擴充套件的,那麼,ASP.NET Web API一定為我們準備了便於擴充套件的介面或抽象類,它就是HttpMessageHandler抽象類。

namespace System.Net.Http
{
    public abstract class HttpMessageHandler : IDisposable
    {
        protected HttpMessageHandler()
        {
        }
        protected internal abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
        protected virtual void Dispose(bool disposing)
        {
        }
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize((object)this);
        }
    }
}

 

這個抽象基類,把處理請求響應交給了SendAsync方法,而且是以非同步的方式處理的。既然這裡沒有提供SendAsync方法的具體實現,所以HttpMessageHandler抽象類一定有一個派生類,它就是DelegatingHandler類。

 

public abstract class DelegatingHandler : HttpMessageHandler
{
    private HttpMessageHandler innerHandler;
    protected DelegatingHandler(HttpMessageHandler innerHandler)
    {
        this.innerHandler = innerHandler;
    }
    public HttpMessageHandler InnerHandler
    {
        get
        {
            return this.innerHandler;
        }
        set
        {
            ...
            this.innerHandler = value;
        }
    }
    protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if(request == null)
        {
            throw new ArgumentNullException("request");
        }
        ...
        return this.innerHandler.SendAsync(request, cancellationToken);
    }
}

 

比較有意思的是,DelegatingHandler本身是一個HttpMessageHandler型別,卻還在它的建構函式中注入一個HttpMessageHandler型別,並且在SendAsync方法中,讓注入的HttpMessageHandler型別執行SendAsync方法,這形成了一個HttpMessageHandler型別的鏈條。從這點來說,訊息處理管道並不是只有一個人在戰鬥,而是,只要派生於DelegatingHandler這個類,不管是內建的,還是自定義的,都可以對請求響應作處理。

 

而在訊息處理管道中肯定有一個打頭陣的人,這個人就是HttpServer類。

public class HttpServer : DelegatingHandler
{
    public HttpConfiguration Configuration{get;}
    public HttpMessageHandler Dispatcher{get;}
    public HttpServer();
    public HttpServer(HttpMessageHandler dipatcher);
    public HttpServer(HttpConfiguration configuration);
    public HttpServer(HttpConfiguration configuration, HttpMessageHandler dispatcher);
    protected overrde void Dispose(bool disposing); //處理HttpConfiguration物件,因為該物件實現了IDisposable介面
    protected virutal void Initialize();
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cacellationToken);
    //別忘了,HttpServer的父類DelegatingHandler還有一個InnerHandler屬性。
}

 

這裡的HttpServer當然是可以例項化的,每一次例項化意味著建立了HttpMessageHandler型別的鏈條的頭,就是HttpServer本身,也建立了鏈條的尾,就是Dispatcher屬性所表示的HttpMessageHandler型別。

 

HttpConfiguration又是什麼呢?

public class HttpConfiguration : IDisposable
{
    ...
    public Collection<DelegatingHandler> MessageHandlers{get;}
}

 

原來,我們可以從HttpConfiguration的MessageHandlers屬性中獲取所有的HttpMessageHandler型別,當然也可以把自定義的HttpMessageHandler型別註冊到這個MessageHandlers集合中去。

 

到這,已經蠢蠢欲動,躍躍欲試了,自定義DelegatingHandler可以登場了!大致是:

HttpServer自定義DelegatingHandlerHttpControllerDispatcher

 

舉例:重新設定HttpRequestMessage.Method屬性

 

一般情況下,客戶端,比如瀏覽器可以發出GET、POST、HEAD、PUT、DELETE請求,當請求進入訊息處理管道,我們可以通過HttpRequestMessage.Method屬性獲取到這些動作型別。但有些客戶端卻只能發出GET、POST請求中,諸如HEAD、PUT、DELETE等請求必須放到請求報文的X-HTTP-Method-Override屬性中,在這種情況下,我們需要把X-HTTP-Method-Override屬性值賦值給HttpRequestMessage.Method屬性。

 

建立一個ASP.NET MVC4專案,選擇ASP.NET Web API模版,就如"ASP.NET Web API實踐系列02,在MVC4下的一個例項, 包含EF Code First,依賴注入, Bootstrap等"中一樣。

 

自定義一個派生於DelegatingHandler的類。

using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ControlAndRoute.Extension
{
    public class MethodOverrideHandler : DelegatingHandler
    {
        private readonly string[] _methods = {"DELETE", "HEAD", "PUT"};
        private const string _header = "X-HTTP-Method-Override";
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (request.Method == HttpMethod.Post && request.Headers.Contains(_header))
            {
                //從請求頭中獲取X-HTTP-Method-Override的屬性值
                var method = request.Headers.GetValues(_header).FirstOrDefault();
                if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
                {
                    request.Method = new HttpMethod(method);
                }
            }
            return base.SendAsync(request, cancellationToken);
        }
    }
}

 

在App_Start資料夾下的WebApiConfig類中註冊MethodOverrideHandler類。

using System.Web.Http;
using ControlAndRoute.Extension;
namespace ControlAndRoute
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            //註冊自定義的DelegatingHandler
            config.MessageHandlers.Add(new MethodOverrideHandler());
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            // 取消註釋下面的程式碼行可對具有 IQueryable 或 IQueryable<T> 返回型別的操作啟用查詢支援。
            // 若要避免處理意外查詢或惡意查詢,請使用 QueryableAttribute 上的驗證設定來驗證傳入查詢。
            // 有關詳細資訊,請訪問 http://go.microsoft.com/fwlink/?LinkId=279712。
            //config.EnableQuerySupport();
            // 若要在應用程式中禁用跟蹤,請註釋掉或刪除以下程式碼行
            // 有關詳細資訊,請參閱: http://www.asp.net/web-api
            config.EnableSystemDiagnosticsTracing();
        }
    }
}

 

在ValuesController中public void Put(int id, [FromBody]string value)的方法體內打上斷點。開啟Fiddler,輸入如下:

1

 

發出的Put請求,被訊息處理管道接收、處理,程式停在Put方法內的斷點處。

2

 

相關文章