Asp.Net MVC3 詳解Request Processing Pipeline

引子

      很久沒更新了,今天寫點關於Asp.Net MVC的PipeLine。首先我們確認一點,Asp.Net WebFrom和Asp.Net MVC是在.Net平臺下的兩種web開發方式。其實他們都是基於Asp.Net Core的不同表現而已。看下面一張圖,我們就能理解了WebForm和Asp.Net MVC的一個關係了。

那好我們瞭解了Asp.Net平臺下的兩種開發方式,相信大家對於WebForm的Pipeline都非常熟悉了,當然這也是你熟悉Asp.Net開發的必經之路。而看了很多關於Asp.Net MVC的資料很少有把整個Pipeline講的非常清楚的。我暫時將自己淺陋的整理和理解總結如下,歡迎高手拍磚!

第一階段:客戶端請求

客戶端通過瀏覽器、其他軟件、自己編寫WebClinet、模擬HttpRequest等方法來請求一個URL。當然在Asp.Net WebFrom下,所有的請求都是歸結到Handler上,普通的Aspx、Ascx等都是繼承自IHttpHandler接口的一些實例,所以我總結出來:WebFrom下所有的請求都是請求的Handler【不考慮Url重寫】。而做Asp.Net MVC的項目呢,所有的請求是都歸結到Action上,Url應該是直接請求Action。

客戶端發出請求後,此請求就會通過網絡發出,可能經過多個路由、還可能經過域名解析等等....

可能請求的是一個集羣IP或者單個服務器,但是最終肯定只能由一臺Web服務器的來處理此次請求。

第二階段:IIS Web服務器

        當一個請求到達IIS服務器後,Windows系統的內核模塊 HTTP.SYS就能監聽到此次請求,並將此次請求的URL、IP以及端口等信息解析出來並將此請求交給註冊的應用來處理:也就是IIS的站點。請求此時就到達了IIS,IIS【此處僅代表IIS6.0版本】就會去檢查此次請求的URL的後綴並將相應的請求交給配置的處理後綴相應的isapi。如果是.aspx或者ascx等直接交給默認設置了此處理項的AspNet_isapi.dll來處理,如果我們想處理Asp.Net MVC的請求的話,我們需要在IIS裏面設置處理*.*請求交給AspNet_isapi.dll來處理,才能將一個普通的MVC請求的URL:Http://localhost/DemoController/DemoAction交給AspNet_Isapi.dll來處理。

第三階段:Asp.Net 運行時

此時請求到AspNet_Isapi.dll後,它負責啓動Asp.Net RunTime【如過啓動了,直接將請求交給RunTime】。Asp.Net 運行時【HttpRuntime】此時會初始化一下HttpContext上下文,並從HttpApplicationFactory去創建一個HttpApplication對象,並將HttpContext賦值給HttpApplication,此後HttpContext的信息就會一直在管道內往下傳遞。

HttpApplication對象開始初始化WebConfig文件中註冊的IHttpModule,請求帶着請求信息【HttpContext】隨着管道流過多個HttpModule【一般可以做爲權限校驗、行爲記錄、日誌等等,就是在到達Handler之前我們都可以直接處理此次Http請求,甚至可以重寫URL】,當然也會經過我們註冊的一些自定義的IHttpModule,在.Net 4.0的machine  的config文件中默認配置了一個URLRouteModule,這個也就是我們普通的Asp.Net MVC項目中的路由DLL引用【System.Web.Routing】內部的一個實現了IHttpModule接口的實例類。請求最終流向了路由組件。

第四階段:Routing組件

如果你用的是MVC 2+ .NET 3.5,則你會在你的web項目中發現UrlRoutingModule就配置在你的Web.Config。.NET 4卻是在.Net的默認配置文件中配置的。

UrlRoutingModule做了這麼幾個工作:首先他會拿着你的請求到路由表中去匹配相應的路由規則。而路由表規則的定義是在HttpApplication初始化的時候由靜態方法執行的,且看一個普通的Asp.Net MVC項目的Global.asax

複製代碼
 public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)//定義路由表規則
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // 路由名稱
"{controller}/{action}/{id}", // 帶有參數的 URL
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 參數默認值
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);//註冊路由表
}
}
複製代碼

而路由表的規則的註冊是在 Application_Start() 方法內部,那此時請求在URLRouteModule內部到路由表中的所有規則進行匹配,並把匹配的Controller的信息和Action的信息以及RouteData等信息都解析處理,然後將請求進一步交給:實現了IRouteHandler【實現了IHttpHandler接口】 的一個實例,下面是IRouteHandler的源碼:

複製代碼
namespace System.Web.Routing
{
public interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}
}
複製代碼

如果你想自己來實現這個接口然後在Web.Config中配置一下,那麼請求就到了你自己的自定義的RouteHandler來執行後續的請求處理操作了。如果你使用的是默認的配置,那麼請求會傳遞到MvcRouteHandler,那麼請求f附加着HttpContext就會到達Asp.Net MVC的處理中了。

第五階段:MvcRouteHandler創建Controller

請求到此,其實跟WebForm都是一致的,而後面纔出現了一些不同,此時請求才真正的進入System.Web.Mvc控制的領域內。後面所有的東西我們都可以直接通過源碼來介紹了,而上面的所有的請求處理只能通過反射等方式來看或者學習,而後面的內容,我們可以幸福的直接看源碼了。那就跟我走進它的管道怎麼流動的吧...

接着上面講,請求到了MvcRouteHandler類,而此類的源碼如下:

複製代碼
namespace System.Web.Mvc
{
using System.Web.Routing;
using System.Web.SessionState;
public class MvcRouteHandler : IRouteHandler
{
private IControllerFactory _controllerFactory;
public MvcRouteHandler()
{
}
public MvcRouteHandler(IControllerFactory controllerFactory)
{
_controllerFactory = controllerFactory;
}
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)//實現了IRouteHandler的方法,URLRouteModule調用
{
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
}
.....
}
複製代碼

MvcRouteHandler的GetHttpHandler方法被URLRouteModule調用,而看上面的紅色源碼部分我們看到,它將請求上下文交給了MVCHandler,並返回了MVCHandler。

而我查看源碼得知:MVCHandler實現了IHttpHandler,此時它的ProcessRequest方法被調用。且看MVCHandler的部分源代碼:

複製代碼
 public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
{
protected internal virtual void ProcessRequest(HttpContextBase httpContext)
{
SecurityUtil.ProcessInApplicationTrust(() =>
{
IController controller;//在ProcessRequestInit方法中:controller = factory.CreateController(RequestContext, controllerName);//初始化
                IControllerFactory factory;//是由ProcessRequestInit方法中這行代碼初始化的: factory = ControllerBuilder.GetControllerFactory();
                ProcessRequestInit(httpContext, out controller, out factory);//初始化了ControllerFactory
try
{
controller.Execute(RequestContext);
}
finally
{
factory.ReleaseController(controller);
}
});
}
}
複製代碼

從源碼中我們得知:請求交給MVCHandler後,它首先從ControllerBuilder獲取到當前的實現了IControllerFactory接口的ControllerFactory【也可以自己定義相關的CustomerControllerFactory,然後在Glable中註冊使用】。然後根據上下文中請求的Controller的字符串信息創建出實現了IController接口的控制器。然後調用了上面代碼中紅色部分,也就是controller.Execute(RequestContext);

那此時請求就交給了controller。

第六階段:Controller調用Action返回ActionResult

由於此文過長,而且時間已經到了凌晨。源碼我就不貼了,簡單介紹一下流程,後面再做詳細贅述。

Controller的Execute方法是在基類ControllerBase中的方法,而此方法又調用ExecuteCore方法,然後此方法內部執行如下代碼:

string actionName = RouteData.GetRequiredString("action");
if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
{
HandleUnknownAction(actionName);
}

首先從RouteData中獲取Action的名字,然後調用ActonInvoker的InvokeAction方法,調用Action執行。Action的返回的ActionResult的ExecuteResult(controllerContext)方法被執行,那此時就出現了分叉。如果直接返回的非ViewResult的話,那就直接協會到Respose流了返回客戶端了,如果是ViewResult的話,那就進入View的領域了。

第七階段:View視圖加載成Page類,並Render成Html

此時請求到ViewResult後,ExecuteResult方法被調用,且看此方法的內部實現:

複製代碼
 public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (String.IsNullOrEmpty(ViewName))
{
ViewName = context.RouteData.GetRequiredString("action");
}

ViewEngineResult result = null;

if (View == null)
{
result = FindView(context);//通過視圖引擎獲取到ViewEngineResult ,此時模板頁面【aspx】被加載成了對應的ViewPage類
View = result.View;
}

TextWriter writer = context.HttpContext.Response.Output;
ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer);
View.Render(viewContext, writer);

if (result != null)
{
result.ViewEngine.ReleaseView(context, View);
}
}
複製代碼

       內部主要是通過ViewResult的FindView方法通過ViewEngine去加載具體的Aspx頁面或者是cshtml頁面生成對應的page類【針對Aspx】,然後再調用IView接口的Render方法將請求信息+ViewData的信息以等一塊渲染成Html並寫回到客戶端。

在此階段我們發現IViewEngine內部的實現這是到規定路徑下去加載Aspx頁面生成對應的ViewPage類。

IView接口的Render方法纔是真正的去將Html和數據裝配的到一塊。

自此請求結束。

總結:

客戶端請求→路由器→IIS服務器內核模塊HTTP.SYS→IIS→AspNet_isapi.dll→Asp.Net Runtime→Application→IHttpModule....IHttpModule→MVCRouteModule→MVCRouteHandler→MVCHandler→ControllerFactory

→Controller→ActionInvoke→Aciton→ActiongResult.ExcuteReuslt()【如果是ViewResult】→IViewEngine FindView→IView Render→Response

最後附兩張關於此請求管道的兩張圖,以饗讀者。

 

 

記於 2011年10月12日0:24:42
轉自博客園Flydragonhttp://www.cnblogs.com/fly_dragon

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章