1. 程式人生 > >mvc原理:打造自己的MVC框架1.0

mvc原理:打造自己的MVC框架1.0

一、MVC的原理

從請求到服務端接受到請求中間這個過程經歷了哪些步驟:

第一步:請求被UrlRoutingModule部件攔截

第二步:封裝請求上下文HttpContext,成為HttpContextWrapper

第三步:根據當前的HttpContext,從Routes集合中得到與當前請求相符合的RouteData物件。

第四步:將RouteData與HttpContext請求封裝成一個RequestContext物件

第五步:根據RequestContext物件,從RouteData的RouteHandler中獲取IHttpHandler(MVC裡面會有一個IHttpHandler的實現類MvcHandler

第六步:執行IHttpHandler(MvcHandler),然後就是通過反射啟用具體的controller,執行具體的action。

大致的執行圖是這個樣子:

1.整個過程有兩個核心的元件。分別是UrlRoutingModule和MvcHandler,這兩個元件都和IHttpModule和IHttpHandler介面,熟悉Asp.Net管線事件的朋友都應該會記得這兩個介面,在管道事件裡面這兩個介面扮演者重要角色。要理解mvc的原理,必須要理解這兩類介面的原理以及使用。

2.UrlRoutingModule的作用可以理解為通過一系列與路由相關的元件去解析當前請求的Controller和Action名稱,其實簡單的理解,比如說我們請求http://localhost:8080/Home/Index這個url的時候,UrlRoutingModule攔截到這個請求,然後通過一系列的方式得到這裡的“Home”和“Index”,這樣理解有沒有簡單一點呢。

3.MvcHandler作用更加直接,上述通過攔截元件得到了請求的Controller和Action的名稱,MvcHandler元件將當前請求的Controller名稱反射得到對應的控制器物件,然後執行對應的Action方法。比如還是上述http://localhost:8080/Home/Index這個請求,通過字串“Home”反射成為Home這個型別的控制器物件,然後呼叫這個物件的Index()方法。

4.綜上,聯合這兩個元件來理解,UrlRoutingMudule元件的主要作用是解析當前的Controller與Action名稱,MvcHandler的作用是將得到的Controller名稱啟用,得到具體的Controller物件,然後執行對應的Action方法

二:HttpHandler

上文說過MvcHandler是繼承至IHttpHandler介面的!為什麼這裡大標題會用HttpHandler而不是MvcHandler呢?因為博主覺得,HttpHandler實在是太重要了,首先得理解了HttpHandler這麼一個大的東西,然後再來看具體的MvcHandler才有意義。

1.HttpHandler指所有實現IHttpHandler介面一類型別的統稱,它是一個大的稱謂。這些型別有一個共同的功能,那就是可以用來處理Http請求。

2.IHttpHandler是微軟定義的一類介面,用來約束所有能夠處理Http請求的型別的介面規則。

3.MvcHandler是Mvc裡面實現IHttpHandler介面的型別,也就是說,MvcHandler是Mvc裡面處理Http請求的型別

總而言之,HttpHandler只是一個邏輯稱謂,它並不具體存在。而IHttpHandler和MvcHandler是.net framework裡面具體存在的介面和實現類,是前者的表現形式。

2、IHttpHandler解析

2.1、Asp.net管線事件簡易說明

做過Webform開發的園友應該記得,在asp.net的頁面生命週期裡面,一共有24個管線事件,完整的管線事件可參考MSDN文件:

在處理該請求時將由 HttpApplication 類執行以下事件。 希望擴充套件 HttpApplication 類的開發人員尤其需要注意這些事件。
1. 對請求進行驗證,將檢查瀏覽器傳送的資訊,並確定其是否包含潛在惡意標記。 有關更多資訊,請參見 ValidateRequest 和指令碼侵入概述。
2. 如果已在 Web.config 檔案的 UrlMappingsSection 節中配置了任何 URL,則執行 URL 對映。
3. 引發 BeginRequest 事件。
4. 引發 AuthenticateRequest 事件。
5. 引發 PostAuthenticateRequest 事件。
6. 引發 AuthorizeRequest 事件。
7. 引發 PostAuthorizeRequest 事件。
8. 引發 ResolveRequestCache 事件。
9. 引發 PostResolveRequestCache 事件。
10. 根據所請求資源的副檔名(在應用程式的配置檔案中對映),選擇實現 IHttpHandler 的類,對請求進行處理。 如果該請求針對從 Page 類派生的物件(頁),並且需要對該頁進行編譯,則 ASP.NET 會在建立該頁的例項之前對其進行編譯。
11. 引發 PostMapRequestHandler 事件。
12. 引發 AcquireRequestState 事件。
13. 引發 PostAcquireRequestState 事件。
14. 引發 PreRequestHandlerExecute 事件。
15. 為該請求呼叫合適的 IHttpHandler 類的 ProcessRequest 方法(或非同步版 IHttpAsyncHandler.BeginProcessRequest)。 例如,如果該請求針對某頁,則當前的頁例項將處理該請求。 
16. 引發 PostRequestHandlerExecute 事件。
17. 引發 ReleaseRequestState 事件。
18. 引發 PostReleaseRequestState 事件。
19. 如果定義了 Filter 屬性,則執行響應篩選。
20. 引發 UpdateRequestCache 事件。
21. 引發 PostUpdateRequestCache 事件。
22. 引發 EndRequest 事件。
23. 引發 PreSendRequestHeaders 事件。
24. 引發 PreSendRequestContent 事件。

這裡不可能把每個管線事件將清楚,但是在整個管線事件中,有兩個重要的角色就是HttpHandlerHttpModule。在這些事件中,第10個事件【根據所請求資源的副檔名(在應用程式的配置檔案中對映),選擇實現 IHttpHandler 的類,對請求進行處理】 是HttpHandler建立的地方。關於WebForm裡面HttpHandler建立的詳細過程,這裡就不展開說了,如果有興趣可以參考http://www.cnblogs.com/fish-li/archive/2012/01/29/2331477.html

2.2、Asp.net中常見的HttpHandler型別

首先還是來看看IHttpHandler的定義

public interface IHttpHandler
{
   //定義一個處理當前http請求的方式
   void ProcessRequest(HttpContext context);
   //指定當前例項是否可以再次使用
   bool IsReusable{get;}
}

介面的定義很簡單,ProcessRequest()方法裡面傳一個當前請求的上下文物件去處理當前的http請求。為了處理非同步請求,Framework裡面還定義了一個非同步的IHttpHandler介面:

public interface IHttpAsyncHandler : IHttpHandler
{
    // Methods
    IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData);
    void EndProcessRequest(IAsyncResult result);
}

介面的兩個方法應該也不難理解。

我們已經說了,HttpHandler的主要作用是處理http請求,原來在做webform的時候應該都寫過後綴ashx的一般處理程式吧,這個一般處理程式就是通過實現IHttpHandler介面去實現的。我們是否曾經也寫過類似這樣的程式碼,新建一個TestHttpHandler.ashx檔案,程式碼如下:

public class TestHttpHandler : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";

            var username = context.Request.QueryString["username"];
            var password = context.Request.QueryString["password"];
            if (username == "admin" && password == "admin")
            {
                context.Response.Write("使用者admin登入成功");
            }
            else
            {
                context.Response.Write("使用者名稱或者密碼錯誤");
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

除了這個,我們還有最常見的aspx頁面

public partial class TestPage : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }
    }

將page類轉到定義

發現原來Page類也是繼承至IHttpHandler,這就是為什麼我們可以通過地址http://localhost:16792/TestPage.aspx來訪問這個頁面的原因。當然,子類中的ProcessRequest()方法並沒有顯示的宣告出來,因為在Page類裡面已經有一個virtue的虛方法,如果需要,你也可以在TestPage這個類裡面顯示宣告:

 public partial class TestPage : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        public void ProcessRequest(HttpContext context)
        {
            context.Response.Write("你好");
        }
    }

然後你會發現這個時候請求會進到ProcessRequest()方法,而不會進到Page_Load()裡面了,至於原因,這和Page類裡面的封裝有關係。當然這不是本文的重點,本文要說明的是所有實現了IHttpHandler介面的型別都可以在ProcessRequest()方法裡面處理當前http請求。

當然,除了ashx和aspx以外,還有一類http的服務介面處理檔案asmx也和IHttpHandler有著不可分割的聯絡,可以說,在asp.net裡面,只要是處理Http請求的地方,IHttpHandler幾乎“無處不在”。

2.3、自定義HttpHandler。

當然,除了上述asp.net自帶的HttpHandler之外,我們也可以自定義HttpHandler處理特定的請求。比如我們新建一個TestMyHandler.cs頁面:

public class TestMyHandler:IHttpHandler
{
    public bool IsReuusable
    {
       get{return false;}
    }
}
public void ProcessRequest(HttpContext context)
{
    context.Response.Write("從asex頁面進來");
}

不過,要使用這個自定義的Handler需要在web.config裡面加上配置

<system.webServer>
   <handlers>
        <add name="asex" verb="*" path="*.asex" type="MyTestMVC.TestMyHandler, MyTestMVC" preCondition="integratedMode" />
    </handlers>
</system.webServer>

3.MvcHandler解析

上文介紹了那麼多IHttpHandler的用法,都是在WebForm裡面的一些實現,我們知道了所有實現了IHttpHandler的類都可以處理Http請求。同樣在MVC裡面,也定義了一個實現IHttpHandler介面的型別——MvcHandler,用於處理當前的http請求。通過反編譯工具可以看到:

public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
{
    // 省略若干欄位// 所有方法
    static MvcHandler();
    public MvcHandler(RequestContext requestContext);
    protected internal virtual void AddVersionHeader(HttpContextBase httpContext);
    protected virtual IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state);
    protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state);
    protected internal virtual void EndProcessRequest(IAsyncResult asyncResult);
    private static string GetMvcVersionString();
    protected virtual void ProcessRequest(HttpContext httpContext);
    protected internal virtual void ProcessRequest(HttpContextBase httpContext);
    private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory);
    private void RemoveOptionalRoutingParameters();
    IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData);
    void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result);
    void IHttpHandler.ProcessRequest(HttpContext httpContext);

    // 省略若干屬性
}

MvcHandler實現了IHttpHandler、 IHttpAsyncHandler兩個介面,非同步請求這裡先不做介紹。重點還是來看看ProcessRequest()方法

將HttpContext轉換為HTTPContextBase物件,繼續轉到定義

這裡聲明瞭一個IController和IControllerFactory物件,通過this.ProcessRequestInit()方法建立具體的Controller例項。我們將ProcessRequestInit()方法轉到定義

 private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
        {
            //1.得到當前的上下文
            HttpContext current = HttpContext.Current;
            if (current != null && ValidationUtility.IsValidationEnabled(current) == true) ValidationUtility.EnableDynamicValidation(current);
            this.AddVersionHeader(httpContext);
            this.RemoveOptionalRoutingParameters();

            //2.從路由物件RouteData中獲取當前請求的Controller名稱
            string requiredString = this.RequestContext.RouteData.GetRequiredString("controller");

            //3.得到Controller工廠物件
            factory = this.ControllerBuilder.GetControllerFactory();

            //4.根據當前RequestContext物件,從Controller工廠建立具體的Controller物件
            controller = factory.CreateController(this.RequestContext, requiredString);
            if (controller == null) throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[] { factory.GetType(), requiredString }));
        }

通過上文的註釋很好理解整個控制器的例項化過程。本打算看下Controller工廠如何建立以及控制器如何例項化的,奈何這部分反編譯不了。我們暫且理解為反射吧,這些實現細節並不影響我們理解整個過程。

建立控制器成功之後,就是執行Action方法了,這個過程在上面反編譯的第二張圖片的 controller.Execute(this.RequestContext); 方法得到體現。所以,除去細節,理解MvcHandler的ProcessRequest()方法並不是太難

三、HttpModule

除了HttpHandler之外,Asp.net裡面還有另外一個重要的角色——HttpModule。和HttpHandler類似,HttpModule指所有實現了IHttpModule介面的一類型別的統稱。至於HttpModule、IHttpModule、UrlRoutingModule各個名稱的含義和上述HttpHandler相同,在此不做重複說明。

1、HttpModule能幹什麼

通過上文,我們知道HttpHandler的作用非常明確:處理Http請求,生成相應結果。那麼,HttpModule又是幹什麼的呢?

HttpHandler的作用是處理某一類別的請求,比如ashx、aspx、asmx等,在某些情況下,各類請求可能都需要進行某些相同的處理(比如請求攔截、身份認證、檢查功能等),不可能在每個類別的HttpHandler裡面都去實現這些相同的程式碼,這個時候怎麼辦呢?處理某一類通用請求,提高程式碼的複用率。是不是想到了我們的面向切面程式設計(AOP),沒錯,HttpModule就是負責做這個事,HttpModule通過事件訂閱的方式,將某類HttpHandler都需要的功能抽取出來,這些功能可以編譯成類庫供各個模組呼叫。這種採用事件(觀察者)的設計模式使得系統設計上更加靈活。

2、HttpModule的使用

先來看看IHttpModule的定義

public interface IHttpModule
{
    //初始化
    void Init(HttpApplication context);
   
    //釋放
    void Dispose();
}

介面定義很簡單,一個初始化元件的方法,一個釋放物件的方法。

我們來寫一個測試的例子具體看看HttpModule如何註冊事件,我們新建一個IHttpModule的實現類

namespace MyTestMVC
{
    public class TestMyModule:IHttpModule
    {
        public void Dispose()
        {
            //throw new NotImplementedException();
        }

        public void Init(HttpApplication app)
        {
            //事件註冊
            app.BeginRequest += app_BeginRequest;
            app.EndRequest += app_EndRequest;
        }

        void app_EndRequest(object sender, EventArgs e)
        {
            var app = (HttpApplication)sender;
            app.Context.Response.Write("請求結束");
        }

        void app_BeginRequest(object sender, EventArgs e)
        {
            var app = (HttpApplication)sender;
            app.Context.Response.Write("請求開始");
        }
    }
}

在Init方法裡面,通過HttpApplication物件來註冊請求的事件。這樣,每次發起一次http請求的時候都進到這兩個方法。

當然,這些註冊就能執行了嗎?想得美,系統哪裡知道你這個自定義HttpModule的存在,所以必須要在Web.config裡面宣告一下。