前言:最近一段時間在學習MVC源碼,說實話,研讀源碼真是一個痛苦的過程,好多晦澀的語法搞得人暈暈乎乎。這兩天算是理解了一小部分,這裏先記錄下來,也給須要的園友一個參考,奈何博主技術有限,若有理解不妥之處,還但願你們斧正,博主感激涕零!html
本文原創地址:http://www.cnblogs.com/landeanfen/p/5989092.htmlgit
MVC源碼學習系列文章目錄:github
最近園子裏Asp.Net Core火了一陣,無論微軟的開源動做有多麼遲緩,仍是但願微軟可以給力一次。做爲Core的主要Web框架——MVC,雖然已經開源,可是讀起來着實費勁,而且感受不少核心部件都找不到。因而只能經過Reflector去反編譯MVC5的組件以及參考博客園Fish Li等大神的文章去學習下MVC5的原理。web
10月26日更新:感謝園友Adming在評論中提醒,原來Asp.net Core Mvc和Asp.net Mvc 5的原理已經徹底不一樣,難怪在Core Mvc的源碼裏面已經找不到MvcHandler、UrlRoutingModule等核心部件了呢,此係列文章就先學習下MVC5的原理,等之後有空了再來研究Core MVC吧。編程
Asp.Net Core MVC的開源地址:https://github.com/aspnet/Mvc設計模式
Asp.net MVC的開源地址:http://aspnetwebstack.codeplex.com/SourceControl/latest瀏覽器
以前的文章有介紹MVC的路由機制,其實路由機制算是MVC的原理的核心之一。在此咱們仍是要不厭其煩再來談談整個過程,由於這是理解MVC原理不可逾越的鴻溝。當咱們收到一個URL的請求時,服務端收到請求,主要經歷如下幾個步驟:緩存
附上一張大體的流程圖:mvc
縱觀整個過程,看上去很複雜,各類對象纏繞,看得人暈暈的。其實若是你靜下心來仔細研讀MVC的源碼你會發現其實並無想像中的那般複雜,請有點耐心聽博主慢慢道來。app
一、整個過程有兩個核心的組件,文中博主用紅色標記了出來:UrlRoutingModule和MvcHandler,上文提到的各個過程都和兩個組件有緊密的聯繫。而這兩個組件分別繼承至IHttpModule和IHttpHandler接口,熟悉Asp.net管線事件的朋友應該會記得這兩個接口,在管道事件裏面這兩個接口扮演着重要角色。要理解MVC的上述原理,必需要先理解這兩類接口的原理以及使用。
二、UrlRoutingModule的做用能夠理解爲經過一系列的與路由相關的組件去解析當前請求的Controller與Action名稱,其實簡單點理解,好比咱們請求http://localhost:8080/Home/Index這個url的時候,UrlRoutingModule攔截到這個請求,而後經過一系列的方式獲得這裏的「Home」和「Index」,這樣理解有沒有簡單一點呢。
三、MvcHandler的做用就更加直接,上述經過攔截組件獲得了請求的Controller和Action的名稱,MvcHandler組件將當前請求的Controller名稱反射獲得對應的控制器對象,而後執行對應的Action方法。好比仍是上述http://localhost:8080/Home/Index這個請求,經過字符串「Home」反射成爲Home這個類型的控制器對象,而後調用這個對象的Index()方法。
四、綜上,聯合這兩個組件來理解,UrlRoutingMudule組件的主要做用是解析當前的Controller與Action名稱,MvcHandler的做用是將獲得的Controller名稱激活,獲得具體的Controller對象,而後執行對應的Action方法。
因此,要理解MVC的原理,必需要了解這兩個組件的基本原理以及做用。下面就根據這兩個組件分別展開說明,相信理解了下面的內容,你對mvc的原理會有一個新的認識。
上文說過MvcHandler是繼承至IHttpHandler接口的!爲何這裏大標題會用HttpHandler而不是MvcHandler呢?由於博主以爲,HttpHandler實在是過重要了,首先得理解了HttpHandler這麼一個大的東西,而後再來看具體的MvcHandler纔有意義。
總而言之,HttpHandler只是一個邏輯稱謂,它並不具體存在。而IHttpHandler和MvcHandler是.net framework裏面具體存在的接口和實現類,是前者的表現形式。
作過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 事件。
這裏不可能把每一個管線事件將清楚,可是在整個管線事件中,有兩個重要的角色就是HttpHandler和HttpModule。在這些事件中,第10個事件【根據所請求資源的文件擴展名(在應用程序的配置文件中映射),選擇實現 IHttpHandler 的類,對請求進行處理】 是HttpHandler建立的地方。關於WebForm裏面HttpHandler建立的詳細過程,這裏就不展開說了,若是有興趣能夠參考http://www.cnblogs.com/fish-li/archive/2012/01/29/2331477.html。
首先仍是來看看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; } } }
而後運行,經過http://localhost:16792/TestHttpHandler.ashx?username=admin&password=admin去訪問通常處理程序,便可獲得正確的結果。
固然,除了這個,還有咱們最多見的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幾乎「無處不在」。
固然,除了上述asp.net自帶的HttpHandler以外,咱們也能夠自定義HttpHandler處理特定的請求。好比咱們新建一個TestMyHandler.cs頁面:
public class TestMyHandler:IHttpHandler { public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { context.Response.Write("從asex頁面進來"); //throw new NotImplementedException(); } }
固然,要使用這個自定義的Handler須要在web.config裏面加上配置。(PS:這部分是博主後來加上的,因此直接用正確的配置)
<system.webServer> <handlers> <add name="asex" verb="*" path="*.asex" type="MyTestMVC.TestMyHandler, MyTestMVC" preCondition="integratedMode" /> </handlers> </system.webServer>
這個配置的意思是全部的url以asex結尾的請求都交給TestMyHandler這個類去處理。獲得效果:
上文介紹了那麼多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()方法並非太難。
除了HttpHandler以外,Asp.net裏面還有另一個重要的角色——HttpModule。和HttpHandler相似,HttpModule指全部實現了IHttpModule接口的一類類型的統稱。至於HttpModule、IHttpModule、UrlRoutingModule各個名稱的含義和上述HttpHandler相同,在此不作重複說明。
經過上文,咱們知道HttpHandler的做用很是明確:處理Http請求,生成相應結果。那麼,HttpModule又是幹什麼的呢?
HttpHandler的做用是處理某一類別的請求,好比ashx、aspx、asmx等,在某些狀況下,各種請求可能都須要進行某些相同的處理(好比請求攔截、身份認證、檢查功能等),不可能在每一個類別的HttpHandler裏面都去實現這些相同的代碼,這個時候怎麼辦呢?處理某一類通用請求,提升代碼的複用率。是否是想到了咱們的面向切面編程(AOP),沒錯,HttpModule就是負責作這個事,HttpModule經過事件訂閱的方式,將某類HttpHandler都須要的功能抽取出來,這些功能能夠編譯成類庫供各個模塊調用。這種採用事件(觀察者)的設計模式使得系統設計上更加靈活。
先來看看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裏面聲明一下。
<system.web> <httpModules> <add name="TestMyModule" type="MyTestMVC.TestMyModule, MyTestMVC" /> </httpModules> </system.web>
出現結果:
查閱資料後發現,原來IIS經典模式下必需要這樣配置:
<system.webServer> <modules> <add name="TestMyModule" type="MyTestMVC.TestMyModule, MyTestMVC" preCondition="integratedMode" /> </modules> </system.webServer>
沒辦法,用微軟的東西就要遵照別人的遊戲規則。改爲這樣以後獲得結果:
文中的「你好」來自這裏:
既然HttpModule是事件註冊機制的,那麼若是須要在同一個事件裏面去實現不一樣的功能,也就是說同一個事件註冊屢次是否可行呢?咱們來試一把:
假如TestMyModule.cs這個自定義Module的做用是功能檢查:
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("功能檢查開始"); //功能檢查的處理邏輯... } }
而後新建一個TestMyModule2.cs這個自定義Module,去實現請求攔截的功能:
public class TestMyModule2: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("請求攔截開始"); //請求攔截的處理邏輯.... } }
最後在Web.config裏面配置兩個Module:
<system.webServer> <modules> <add name="TestMyModule" type="MyTestMVC.TestMyModule, MyTestMVC" preCondition="integratedMode" /> <add name="TestMyModule2" type="MyTestMVC.TestMyModule2, MyTestMVC" preCondition="integratedMode" /> </modules> </system.webServer>
獲得結果:
這說明同一個事件能夠註冊屢次,便可以在同一個事件裏面作不一樣的事。
經過上文的HttpModule的應用,咱們看到在Init方法裏面能夠拿到當前應用的HttpApplication對象,拿到這個貌似就能夠拿到當前請求上下文裏面的Request、Response了,是否是就能夠處理當前的http請求了,從這點上來講,HttpModule也能處理http請求,或者說具備處理http請求的能力。既然HttpHandler和HttpModule均可以處理http請求,那在使用的時候如何區分呢?上文說過,HttpModule的做用相似AOP,是針對某些通用功能(請求攔截、身份認證、檢查功能)的,而HttpHandler經常使用來處理某一類(ashx、aspx、asmx)http請求,二者的側重點不一樣,至於具體在實際中如何使用,你能夠自行考量。
好了,上面介紹那麼多HttpModule的使用,都是在爲了解Mvc裏面的UrlRoutingModule作鋪墊。上文說過UrlRoutingModule的做用是攔截請求,那麼它是如何作的呢,仍是來反編譯看看吧。
public class UrlRoutingModule : IHttpModule { // Fields private static readonly object _contextKey; private static readonly object _requestDataKey; private RouteCollection _routeCollection; // Methods static UrlRoutingModule(); public UrlRoutingModule(); protected virtual void Dispose(); protected virtual void Init(HttpApplication application); private void OnApplicationPostResolveRequestCache(object sender, EventArgs e); [Obsolete("This method is obsolete. Override the Init method to use the PostMapRequestHandler event.")] public virtual void PostMapRequestHandler(HttpContextBase context); public virtual void PostResolveRequestCache(HttpContextBase context); void IHttpModule.Dispose(); void IHttpModule.Init(HttpApplication application); // Properties public RouteCollection RouteCollection { get; set; } }
重點確定在Init()方法。
圖一:
註冊HttpApplication對象的PostResolveRequestCache事件。
圖二:
封裝HttpContext,成爲HttpContextWrapper對象
圖三:
這部分代碼是咱們上述路由理論的代碼實踐,因此這段代碼很重要,咱們將代碼拷貝出來:
public virtual void PostResolveRequestCache(HttpContextBase context) { //1.傳入當前上下文對象,獲得與當前請求匹配的RouteData對象 RouteData routeData = this.RouteCollection.GetRouteData(context); if (routeData != null) { //2.從RouteData對象裏面獲得當前的RouteHandler對象。其實這裏的RouteHandler屬性對應就是一個MvcRouteHandler的對象。 IRouteHandler routeHandler = routeData.RouteHandler; if (routeHandler == null) throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0])); if (!(routeHandler is StopRoutingHandler)) { //3.根據HttpContext和RouteData獲得RequestContext對象 RequestContext requestContext = new RequestContext(context, routeData); context.Request.RequestContext = requestContext; //4.根據RequestContext對象獲得處理當前請求的HttpHandler(MvcHandler)。 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); if (httpHandler == null) { object[] args = new object[] { routeHandler.GetType() }; throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), args)); } if (httpHandler is UrlAuthFailureHandler) { if (!FormsAuthenticationModule.FormsAuthRequired) throw new HttpException(0x191, SR.GetString("Assess_Denied_Description3")); UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this); } else //5.請求轉到HttpHandler進行處理(進入到ProcessRequest方法)。這一步很重要,由這一步開始,請求才由UrlRoutingModule轉到了MvcHandler裏面 context.RemapHandler(httpHandler); } } }
博主在主要的地方加上了註釋。
代碼釋疑:這裏有幾點須要說明的。
一、HttpApplication對象的PostResolveRequestCache事件在MSDN上的解釋是:在 ASP.NET 跳過當前事件處理程序的執行並容許緩存模塊知足來自緩存的請求時發生。查閱相關資料發現,之因此在PostResolveRequestCache事件註冊路由、匹配HttpHandler,是爲了知足IIS6。能夠參考Tom大叔的文章:http://www.cnblogs.com/TomXu/p/3756858.html。
二、 IRouteHandler routeHandler = routeData.RouteHandler; 這裏的routeHandler其實是一個MvcRouteHandler類型的對象,爲何這麼說,咱們來反編譯下這個就會一目瞭然:
圖一:
MvcRouteHandler實現了IRouteHandler接口。而後咱們重點來看GetHttpHandler()方法獲得的是哪一個HttpHandler。
圖二:
看到最後一句是否是立馬就明白了。也就是說GetHttpHandler()這個方法決定了採用MvcHandler去處理當前的http請求。因此在上述 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); 這一句獲得的就是一個MvcHandler的實例。
三、 context.RemapHandler(httpHandler); 這一句能夠理解爲將當前的請求上下文交給httpHandler這個對象去處理。
四、到這裏,咱們再反過來看前面的MVC的原理就徹底明朗了。
寫到這裏,總算把整個過程梳理了一遍,不少細節都未涉及,可是大的過程應該仍是明朗的。通篇比較偏理論,因此總體上比較枯燥,可是仍是但願園友們可以靜下心來慢慢看,由於博主以爲這些對於理解MVC原理過重要!!!想一想看,若是你也徹底理解了這個過程,是否是均可以本身經過實現IHttphandler和IHttpModule去搭建一個簡單的MVC框架了,不錯,博主確實是這樣打算的,這篇把理論搞清楚,下篇就是實現的細節了。其實寫本身的MVC框架更多的在於學習MVC原理,但願本身可以堅持下去。若是你以爲本文可以幫助你,能夠右邊隨意 打賞 博主,也能夠 推薦 進行精神鼓勵。你的支持是博主繼續堅持的不懈動力。
本文原創出處:http://www.cnblogs.com/landeanfen/
歡迎各位轉載,可是未經做者本人贊成,轉載文章以後必須在文章頁面明顯位置給出做者和原文鏈接,不然保留追究法律責任的權利