基於asp.net MVC 應用程式的生命週期(詳解)

基於asp.net MVC 應用程式的生命週期(詳解)

首先我們知道http是一種無狀態的請求,他的生命週期就是從客戶端瀏覽器發出請求開始,到得到響應結束。那麼MVC應用程式從發出請求到獲得響應,都做了些什麼呢?

本文我們會詳細討論MVC應用程式一個請求的生命週期,從一個控制元件到另一個控制元件是怎樣被處理的。我們還會詳細介紹一下整個請求的生命週期中,用到的相關元件。因為在平常的開發過程中,我們可能知道怎樣去使用MVC框架來處理相關的請求,大部分的時候我們只是在controller和action方法之間做相關的處理,對於真正內在的執行機制可能不是很瞭解。其實當我們對內在機制有了一定的瞭解以後,會發現微軟的MVC框架的擴充套件性很強,到處都留有擴充套件介面,讓我們通過擴充套件能夠自己定義自己所需要的處理機制,這也正是為什麼MVC框架如此出名的原因。

當我最開始學習使用mvc的時候,困擾我的一個問題就是,一個請求的流程控制是怎樣的呢?從view到controller再到action之間經歷了什麼?那個時候我還不清楚HTTP module和HTTP handler在處理一個請求中扮演什麼樣的角色,起什麼樣的作用呢。畢竟MVC是一個web開發框架,在整個請求處理過程中,肯定包含了http module和http handler。其實還有很多相關的元件包含在一個完整的mvc應用程式請求生命週期裡,在整個請求過程中他們都扮演者非常重要的角色。儘管大部分時候我們都使用的是框架提供的預設的函式,但是如果我們瞭解了每個控制元件所扮演的角色,我們就可以輕鬆的擴充套件和使用我們自己實現的方法,就目前來說MVC是擴充套件性比較強的框架。

下面是本章節的主要內容:

HttpApplication

HttpModule

HttpHandler

ASP.NET MVC執行機制

UrlRoutingModule

RouteHandler

MvcHandler

ControllerFactory

Controller

ActionInvoker

ActionResult

ViewEngine

HttpApplication

我們都知道,在ASP.NET MVC框架出現之前,我們大部分開發所使用的框架都是ASP.NET WebForm.其實不管是MVC還是WebForm,在請求處理機制上,大部分是相同的。這涉及到IIS對請求的處理,涉及的知識較多,我們就不做介紹了,下次有機會我寫一篇專文。我們從HttpApplication說起。先看看微軟官方是怎麼定義HttpApplication的:

定義 ASP.NET 應用程式中的所有應用程式物件共有的方法、屬性和事件。此類是使用者在 Global.asax 檔案中所定義的應用程式的基類。

可能我翻譯不是很準確,原文連線在這裡:https://msdn.microsoft.com/en-us/library/system.web.httpapplication(v=vs.110).aspx

微軟官方文件中Remark裡有這麼一段話:HttpApplication 類的例項是在 ASP.NET 基礎結構中建立的,而不是由使用者直接建立的。使用 HttpApplication 類的一個例項來處理其生存期中收到的眾多請求。但是,它每次只能處理一個請求。這樣,成員變數才可用於儲存針對每個請求的資料。

意思就是說ASP.NET應用程式,不管是MVC還是WebForm,最終都會到達一個HttpApplication類的例項。HttpApplication是整個ASP.NET基礎架構的核心,負責處理分發給他的請求。HttpApplication處理請求的週期是一個複雜的過程,在整個過程中,不同階段會觸發相映的事件。我們可以註冊相應的事件,將處理邏輯注入到HttpApplication處理請求的某個階段。在HttpApplication這個類中定義了19個事件來處理到達HttpApplication例項的請求。就是說不管MVC還是WebForm,最終都要經過這19個事件的處理,那麼除了剛才說的MVC和WebFrom在請求處理機制上大部分都是相同的,不同之處在哪呢?他們是從哪裡開始分道揚鑣的呢?我們猜想肯定就在這19個方法中。我們繼續往下看。

我們來看看這19個事件:

應用程式按照以下順序執行由 global.asax 檔案中定義的模組或使用者程式碼處理的事件:

事件名稱:

簡單描述:

BeginRequest

在 ASP.NET 響應請求時作為 HTTP 執行管線鏈中的第一個事件發生

AuthenticateRequest

當安全模組已建立使用者標識時發生。注:AuthenticateRequest 事件發出訊號表示配置的身份驗證機制已對當前請求進行了身份驗證。預訂 AuthenticateRequest 事件可確保在處理附加的模組或事件處理程式之前對請求進行身份驗證

PostAuthenticateRequest

當安全模組已建立使用者標識時發生。PostAuthenticateRequest 事件在 AuthenticateRequest 事件發生之後引發。預訂 PostAuthenticateRequest 事件的功能可以訪問由 PostAuthenticateRequest 處理的任何資料

AuthorizeRequest

當安全模組已驗證使用者授權時發生。AuthorizeRequest 事件發出訊號表示 ASP.NET 已對當前請求進行了授權。預訂 AuthorizeRequest 事件可確保在處理附加的模組或事件處理程式之前對請求進行身份驗證和授權

PostAuthorizeRequest

在當前請求的使用者已獲授權時發生。PostAuthorizeRequest 事件發出訊號表示 ASP.NET 已對當前請求進行了授權。預訂PostAuthorizeRequest 事件可確保在處理附加的模組或處理程式之前對請求進行身份驗證和授權

ResolveRequestCache

當 ASP.NET 完成授權事件以使快取模組從快取中為請求提供服務時發生,從而跳過事件處理程式(例如某個頁或 XML Web services)的執行

PostResolveRequestCache

在 ASP.NET 跳過當前事件處理程式的執行並允許快取模組滿足來自快取的請求時發生。)在 PostResolveRequestCache 事件之後、PostMapRequestHandler 事件之前建立一個事件處理程式(對應於請求 URL 的頁

PostMapRequestHandler

在 ASP.NET 已將當前請求對映到相應的事件處理程式時發生。

AcquireRequestState

當 ASP.NET 獲取與當前請求關聯的當前狀態(如會話狀態)時發生。

PostAcquireRequestState

在已獲得與當前請求關聯的請求狀態(例如會話狀態)時發生。

PreRequestHandlerExecute

恰好在 ASP.NET 開始執行事件處理程式(例如,某頁或某個 XML Web services)前發生。

PostRequestHandlerExecute

在 ASP.NET 事件處理程式(例如,某頁或某個 XML Web service)執行完畢時發生。

ReleaseRequestState

在 ASP.NET 執行完所有請求事件處理程式後發生。該事件將使狀態模組儲存當前狀態資料。

PostReleaseRequestState

在 ASP.NET 已完成所有請求事件處理程式的執行並且請求狀態資料已儲存時發生。

UpdateRequestCache

當 ASP.NET 執行完事件處理程式以使快取模組儲存將用於從快取為後續請求提供服務的響應時發生。

PostUpdateRequestCache

在 ASP.NET 完成快取模組的更新並儲存了用於從快取中為後續請求提供服務的響應後,發生此事件。

LogRequest

在 ASP.NET 完成快取模組的更新並儲存了用於從快取中為後續請求提供服務的響應後,發生此事件。

僅在 IIS 7.0 處於整合模式並且 .NET Framework 至少為 3.0 版本的情況下才支援此事件

PostLogRequest

在 ASP.NET 處理完 LogRequest 事件的所有事件處理程式後發生。

僅在 IIS 7.0 處於整合模式並且 .NET Framework 至少為 3.0 版本的情況下才支援此事件。

EndRequest

在 ASP.NET 響應請求時作為 HTTP 執行管線鏈中的最後一個事件發生。

在呼叫 CompleteRequest 方法時始終引發 EndRequest 事件。

對於一個ASP.NET應用程式來說,HttpApplication派生與Global.aspx(可以看看我們建立的應用程式都有一個Global.aspx檔案),我們可以在Global.aspx檔案中對HttpApplication的請求進行定製即注入這19個事件中的某個事件進行邏輯處理操作。在Global.aspx中我們按照”Application_{Event Name}”這樣的方法命名進行事件註冊。

Event Name就是上面19個事件的名稱。比如Application_EndRequest就用於處理Application的EndRequest事件。

HttpModule

ASP.NET擁有一個高度可擴充套件的引擎,並且能夠處理對於不同資源型別的請求。這就是HttpModule。當一個請求轉入ASP.net管道時,最終負責處理請求的是與資源相匹配的HttpHandler物件,但是在HttpHandler進行處理之前,ASP.NET先會載入並初始化所有配置的HttpModule物件。HttpModule初始化的時候,會將一些回撥事件注入到HttpApplication相應的事件中。所有的HttpModule都實現了IHttpModule介面,該介面有一個有一個Init方法。


public interface IHttpModule
{
// Methods
void Dispose();
void Init(HttpApplication context);
}

看到Init方法呢接受一個HttpApplication物件,有了這個物件就很容易註冊HttpApplication中19個事件中的某個事件了。這樣當HttpApplication物件執行到某個事件的時候自然就會出發。

HttpHandler

對於不同的資源型別的請求,ASP.NET會載入不同的HttpHandler來處理。所有的HttpHandler都實現了IhttpHandler介面。


public interface IHttpHandler
{
// Methods
void ProcessRequest(HttpContext context);
// Properties
bool IsReusable { get; }
}

我們看到該介面有一個方法ProcessRequest,顧名思義這個方法就是主要用來處理請求的。所以說每一個請求最終分發到自己相應的HttpHandler來處理該請求。

ASP.NET MVC 執行機制

好了,上面說了那麼多,其實都是給這裡做鋪墊呢。終於到正題了。先看看下面這張圖,描述了MVC的主要經歷的管道事件:

上圖就是一個完整的mvc應用程式的一個http請求到響應的整個兒所經歷的流程。從UrlRoutingModule攔截請求到最終ActionResult執行ExecuteResult方法生成響應。

下面我們就來詳細講解一下這些過程都做了些什麼。

UrlRoutingModule

MVC應用程式的入口UrlRoutingModule

首先發起一個請求,我們前面講到ASP.NET 會載入一個HttpModule物件的初始化事件Init,而所有的HttpModule物件都實現了IHttpModule介面。我們看看UrlRoutingModule的實現:

從上圖中我們看到UrlRoutingModule實現了介面IHttpModule,當一個請求轉入ASP.NET管道時,就會載入 UrlRoutingModule物件的Init()方法。

那麼為什麼偏偏是UrlRoutingModule被載入初始化了呢?為什麼不是別的HttpModule物件呢?帶著這個疑問我們繼續。

在ASP.NET MVC中,最核心的當屬“路由系統”,而路由系統的核心則源於一個強大的System.Web.Routing.dll元件。System.Web.Routing.dll 不是MVC所特有的,但是MVC框架和它是密不可分的。

首先,我們要了解一下UrlRoutingModule是如何起作用的。

(1)IIS網站的配置可以分為兩個塊:全域性 Web.config 和本站 Web.config。Asp.Net Routing屬於全域性性的,所以它配置在全域性Web.Config 中,我們可以在如下路徑中找到:“C\Windows\Microsoft.NET\Framework\版本號\Config\Web.config“,我提取部分重要配置大家看一下:


<httpModules>
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule" />
<add name="Session" type="System.Web.SessionState.SessionStateModule" />
<add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
<add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" />
<add name="RoleManager" type="System.Web.Security.RoleManagerModule" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
<add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" />
<add name="Profile" type="System.Web.Profile.ProfileModule" />
<add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />
<add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</httpModules>

大家看到沒有,我上面標紅的那一行:<add name=”UrlRoutingModule-4.0″ type=”System.Web.Routing.UrlRoutingModule” />

UrlRoutingModule並不是MVC特有的,這是一個全域性配置,就是說所有的ASP.NET請求都會到達這裡,所以該Module還不能最終決定是MVC還是WebForm請求。但是也是至關重要的地方。

(2)通過在全域性Web.Config中註冊 System.Web.Routing.UrlRoutingModule,IIS請求處理管道接到請求後,就會載入 UrlRoutingModule型別的Init()方法。其原始碼入下:


[TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
public class UrlRoutingModule : IHttpModule
{
// Fields
private static readonly object _contextKey = new object();
private static readonly object _requestDataKey = new object();
private RouteCollection _routeCollection;
// Methods
protected virtual void Dispose()
{
}
protected virtual void Init(HttpApplication application)
{
if (application.Context.Items[_contextKey] == null)
{
application.Context.Items[_contextKey] = _contextKey;
application.PostResolveRequestCache  = new EventHandler(this.OnApplicationPostResolveRequestCache);
}
}
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication) sender;
HttpContextBase context = new HttpContextWrapper(application.Context);
this.PostResolveRequestCache(context);
}
[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)
{
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData != null)
{
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))
{
RequestContext requestContext = new RequestContext(context, routeData);
context.Request.RequestContext = requestContext;
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
if (httpHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[] { routeHandler.GetType() }));
}
if (httpHandler is UrlAuthFailureHandler)
{
if (!FormsAuthenticationModule.FormsAuthRequired)
{
throw new HttpException(0x191, SR.GetString("Assess_Denied_Description3"));
}
UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
}
else
{
context.RemapHandler(httpHandler);
}
}
}
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
void IHttpModule.Dispose()
{
this.Dispose();
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
void IHttpModule.Init(HttpApplication application)
{
this.Init(application);
}
// Properties
public RouteCollection RouteCollection
{
get
{
if (this._routeCollection == null)
{
this._routeCollection = RouteTable.Routes;
}
return this._routeCollection;
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
set
{
this._routeCollection = value;
}
}
}

看看上面的UrlRoutingModule原始碼裡面是怎麼實現Init方法的,Init()方法裡面我標註紅色的地方:

application.PostResolveRequestCache = new EventHandler(this.OnApplicationPostResolveRequestCache);

這一步至關重要哈,看到沒有,就是對我們在HttpApplication那19個事件中的PostResolveRequestCache事件的註冊。註冊的方法是OnApplicationPostResolveRequestCache事件。也就是說HttpApplication物件在執行到PostResolveRequestCache這個事件的時候,就會執行OnApplicationPostResolveRequestCache事件。決定是MVC機制處理請求的關鍵所在就是OnApplicationPostResolveRequestCache事件。

從原始碼中我們看出,OnApplicationPostResolveRequestCache事件執行的時候,最終執行了PostResolveRequestCache這個方法。最關鍵的地方呢就在這裡了。

當請求到達UrlRoutingModule的時候,UrlRoutingModule取出請求中的Controller、Action等RouteData資訊,與路由表中的所有規則進行匹配,若匹配,把請求交給IRouteHandler,即MVCRouteHandler。我們可以看下UrlRoutingModule的原始碼來看看,以下是幾句核心的程式碼:

我們再分析一下這個方法的原始碼:


public virtual void PostResolveRequestCache(HttpContextBase context)
{
// 通過RouteCollection的靜態方法GetRouteData獲取到封裝路由資訊的RouteData例項
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData != null)
{
// 再從RouteData中獲取MVCRouteHandler
IRouteHandler routeHandler = routeData.RouteHandler;
......
if (!(routeHandler is StopRoutingHandler))
{
......
// 呼叫 IRouteHandler.GetHttpHandler(),獲取的IHttpHandler 型別例項,它是由 IRouteHandler.GetHttpHandler獲取的,這個得去MVC的原始碼裡看
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
......
// 合適條件下,把之前將獲取的IHttpHandler 型別例項 對映到IIS HTTP處理管道中
context.RemapHandler(httpHandler);
}
}
}

看到了吧,通過路由規則,返回的不為空,說明匹配正確,關於路由規則的匹配,說起來也不短,這裡就不大幅介紹,有時間下次再開篇詳解路由機制。匹配成功後,返回一個RouteData型別的物件,RouteData物件都有些什麼屬性呢?看看這行原始碼: IRouteHandler routeHandler = routeData.RouteHandler;或者看原始碼我們知道,RouteDate有一個RouteHandler屬性。

那麼UrlRouting Module是如何選擇匹配規則的呢?

我們看看我們新建的MVC應用程式,在App_Start資料夾下面有一個RouteConfig.cs類,這個類的內容如下:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace ApiDemo
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}

我們在這個類裡面,主要是給路由表新增路由規則。在看看上面的UrlRoutingModule類,裡面有一個RoutCollection屬性,所以UrlRoutingModule能夠獲取路由表中的所有規則,這裡值得注意的是,路由規則的匹配是有順序的,如果有多個規則都能夠匹配,UrlRoutingModule至選擇第一個匹配的規則就返回,不再繼續往下匹配了。相反的如果一個請求,沒有匹配到任何路由,那麼該請求就不會被處理。

這裡返回的RouteData裡的RouteHandler就是MVCRouteHandler。為什麼呢?那我們繼續往下看RouteHandler。

RouteHandler

生成MvcHander

在上面路由匹配的過程中,與匹配路由相關聯的MvcRouteHandler ,MvcRouteHandler 實現了IRouteHandler 介面。MvcRouteHandler 主要是用來獲取對MvcHandler的引用。MvcHandler實現了IhttpHandler介面。

MVCRouteHandler的作用是用來生成實現IHttpHandler介面的MvcHandler。而我們前面說過最終處理請求的都是相對應的HttpHandler。那麼處理MVC請求的自然就是這個MvcHandler。所以這裡返回MvcRouteHandler至關重要:

那麼,MvcRouteHandler從何而來呢?眾所周知,ASP.NET MVC專案啟動是從Global中的Application_Start()方法開始的,那就去看看它:


public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
System.Web.Mvc.RouteCollectionExtensions
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}

看看我上面標紅的程式碼:這是路由註冊,玄機就在這裡。那我們去看看MapRoute原始碼就知道咯:


public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) 
{
......
Route route = new Route(url, new MvcRouteHandler()) {
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};
......
return route;
}

看看我們5-8行程式碼,在MVC應用程式裡,在路由註冊的時候,我們就已經給他一個預設的HttpRouteHandler物件,就是 New MvcRouteHandler().現在我們反推回去,我們MVC程式在路由註冊的時候就已經確定了HttpRouteHandler為MvcRouteHandler,那麼當我們在前面PostResolveRequestCache方法裡,當我們的請求與路由匹配成功後,自然會返回的是MvcRouteHandler。

好啦,MvcRouteHandler生成了。那麼MvcRouteHandler能做什麼呢?又做了什麼呢?

再回頭看看 PostResolveRequestCache方法,在成功獲取到IHttpRouteHandler物件即MvcRouteHandler之後,又做了下面這一個操作:

IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);

我們看看這個IHttpHandler 的原始碼:


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

有一個GetHttpHandler的方法,恰好就呼叫了這個方法。那我們看看MvcRouteHandler是怎麼實現這個GetHttpHandler的呢:


public class MvcRouteHandler : IRouteHandler
{
// Fields
private IControllerFactory _controllerFactory;
// Methods
public MvcRouteHandler()
{
}
public MvcRouteHandler(IControllerFactory controllerFactory)
{
this._controllerFactory = controllerFactory;
}
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.HttpContext.SetSessionStateBehavior(this.GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
}
protected virtual SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext)
{
string str = (string) requestContext.RouteData.Values["controller"];
if (string.IsNullOrWhiteSpace(str))
{
throw new InvalidOperationException(MvcResources.MvcRouteHandler_RouteValuesHasNoController);
}
IControllerFactory factory = this._controllerFactory ?? ControllerBuilder.Current.GetControllerFactory();
return factory.GetControllerSessionBehavior(requestContext, str);
}
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
{
return this.GetHttpHandler(requestContext);
}
}

看第16-20行程式碼,這時候應該明白了吧。順理成章的返回了MvcHandler物件。記得我們前面說過,請求最終是被相對應的HttpHander物件處理的。MvcHandler就是那個用來處理Mvc請求的HttpHandler。MvcRouteHandler把請求交給了MvcHandler去做請求處理管道中後續事件的處理操作了。

下面我們就看看MvcHandler做了些什麼:

MvcHandler

MvcHandler就是最終對request進行處理。

MvcHandler的定義如下:

我們可以看到MvcHandler就是一個普通的Http Handler.我們知道一個http handler需要實現一個ProcessRequest()的方法,這個方法就是處理request的核心。所以MvcHandler實現了ProcessRequest()方法。

ProcessRequest主要功能:

(1)在ASP.NET MVC中,會呼叫MvcHandler的ProcessRequest()方法,此方法會啟用具體請求的Controller類物件,觸發Action方法,返回ActionResult例項。

(2)如果ActionResult是非ViewResult,比如JsonResult, ContentResult,這些內容將直接被輸送到Response響應流中,顯示給客戶端;如果是ViewResult,就會進入下一個渲染檢視環節。

(3)在渲染檢視環節,ViewEngine找到需要被渲染的檢視,View被載入成WebViewPage<TModel>型別,並渲染生成Html,最終返回Html。

ProcessRequest()定義如下:


// Copyright (c) Microsoft Open Technologies, Inc.<pre>// All rights reserved. See License.txt in the project root for license information.
void IHttpHandler.ProcessRequest(HttpContext httpContext) 
{
ProcessRequest(httpContext);
}
protected virtual void ProcessRequest(HttpContext httpContext) 
{
HttpContextBase iHttpContext = new HttpContextWrapper(httpContext);
ProcessRequest(iHttpContext);
}
protected internal virtual void ProcessRequest(HttpContextBase httpContext) {
SecurityUtil.ProcessInApplicationTrust(() => {
IController controller;
IControllerFactory factory;
ProcessRequestInit(httpContext, out controller, out factory);
try
{
controller.Execute(RequestContext);
}
finally
{
factory.ReleaseController(controller);
}
});
}

從上面的程式碼可以看出呼叫了一個ProcessRequestInit()方法,定義如下:


private void ProcessRequestInit(HttpContextBase httpContext, 
out IController controller, out IControllerFactory factory) {
// If request validation has already been enabled, make it lazy.
// This allows attributes like [HttpPost] (which looks
// at Request.Form) to work correctly without triggering full validation.
bool? isRequestValidationEnabled = 
ValidationUtility.IsValidationEnabled(HttpContext.Current);
if (isRequestValidationEnabled == true) {
ValidationUtility.EnableDynamicValidation(HttpContext.Current);
}
AddVersionHeader(httpContext);
RemoveOptionalRoutingParameters();
// Get the controller type
string controllerName = RequestContext.RouteData.GetRequiredString("controller");
// Instantiate the controller and call Execute
factory = ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext, controllerName);
if (controller == null) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.ControllerBuilder_FactoryReturnedNull,
factory.GetType(),
controllerName));
}
}

在ProcessRequestInit()方法中首先建立了ControllerFactory()的物件 factory.然後ControllerFactory建立了相關Controller的例項.最終呼叫了Controller的Excute()方法。

好我們再來看看ControllerFactory:

ControllerFactory

主要是用來生成Controller物件

ControllerFactory實現了介面IControllerFactory.

Controller

到這裡我們大概就知道了,MvcHandler通過ProcessRequest()方法最終建立了Controller物件,這裡我們都應該知道,Controller裡面包含很多的Action方法,每一次請求至少一個Action方法會被呼叫。為了明確的實現IController介面,框架裡面有一個ControllerBase的類已經實現了IController介面,其實我們自己的Controller也可以不繼承ControllerBase,只要實現IController介面即可。


public abstract class ControllerBase : IController
{
protected virtual void Execute(RequestContext requestContext)
{
if (requestContext == null)
{
throw new ArgumentNullException("requestContext");
}
if (requestContext.HttpContext == null)
{
throw new ArgumentException(
MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, 
"requestContext");
}
VerifyExecuteCalledOnce();
Initialize(requestContext);
using (ScopeStorage.CreateTransientScope())
{
ExecuteCore();
}
}
protected abstract void ExecuteCore(); 
// .......

controller物件實際上使用ActionInvoker來呼叫Action方法的,當Controller物件被建立後,會執行Controller物件的基類ControllerBase類裡面的Excute方法。Excute方法又呼叫了ExcuteCore()方法。Controller類裡面實現了ExcuteCore()方法。ExcuteCore呼叫了ActionInvoker的InvokerAction方法來呼叫Action方法。

ActionInvoker

ActionInvoker方法有很重要的責任來查詢Controller中的Action方法並且呼叫。

ActionInvoker是一個實現了IActionInvoker介面的物件:


bool InvokeAction(
ControllerContext controllerContext,
string actionName
) 

Controller類裡面暴露了一個ActionInvoker 屬性,會返回一個ControllerActionInvoker 。ActionInvoker通過CreateActionInvoker()方法來建立ControllerActionInvoker物件。


public IActionInvoker ActionInvoker {
get {
if (_actionInvoker == null) {
_actionInvoker = CreateActionInvoker();
}
return _actionInvoker;
}
set {
_actionInvoker = value;
}
}
protected virtual IActionInvoker CreateActionInvoker() {
return new ControllerActionInvoker();
}

我們看到CreateActionInvoker()是一個Virtual方法,我們可以實現自己的ActionInvoker.

ActionInvoker類需要匹配Controller中詳細的Action來執行,而這些詳細的資訊是由ControllerDescriptor 提供的。ControllerDescriptor 和ActionDescriptor在ActionInvoker中扮演重要的角色。這兩個分別是對Controler和Action的詳細描述。ControllerDescriptor 描述了Controller的相關資訊比如name,action,type等。

ActionDescriptor 描述了Action相關的詳情,比如name,controller,parameters,attributes和fiflters等。

ActionDescriptor 中一箇中要的方法就是FindAction(),這個方法返回一個ActionDescriptor 物件,所以ActionInvoker知道該呼叫哪個Action。

ActionResult

到目前為止,我們看到了Action方法被ActionInvoker呼叫。所有的Action方法有一個特性,就是返回一個ActionResult型別的資料。


public abstract class ActionResult
{
public abstract void ExecuteResult(ControllerContext context);
}

ExecuteResult()是一個抽象方法,所以不同的子類可以提供不同的ExecuteResult()實現。

ActionResult執行後響應輸出到客戶端。

ViewEngine

ViewResult幾乎是大部分應用程式的返回型別,主要通過ViewEngine引擎來展示view的。ViewEngine可能主要就是生成Html元素的引擎。Framwork提供了2種引擎,Razor View Engine 和Web Form View Engine.如果你想自定義引擎,你可以建立一個引擎只要實現IViewEngine介面即可。

IViewEngine 有下面幾個方法:

1、FindPartialView :當controller需要返回一個PartialView的時候,FindPartialView方法 就會被呼叫。

2、FindView

3、ReleaseView :主要用來有ViewEngine釋放資源

ViewResultBase 和ViewResult是比較重要的兩個類。ViewResultBase 包含下面的實現程式碼:


if (View == null)
{
result = FindView(context); //calls the ViewResult's FindView() method
View = result.View;
}
ViewContext viewContext = new ViewContext(context, View, ViewData, TempData);
View.Render(viewContext, context.HttpContext.Response.Output);
protected abstract ViewEngineResult FindView(ControllerContext context); //this is implemented by          //the ViewResult

protected override ViewEngineResult FindView(ControllerContext context)
{
ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);
if (result.View != null)
{
return result;
}
//rest of the code omitted 
}

當ViewResult的方法ExecuteResult被呼叫後,ViewResultBase 的ExecuteResult 方法被呼叫,然後ViewResultBase 呼叫ViewResult的FindView 。緊接著ViewResult 返回ViewEngineResult,之後ViewEngineResult呼叫Render()方法來繪製html輸出響應。

總結:如果我們理解了整個過程中發生了什麼,哪些類和哪些方法被呼叫,我們就可以在需要擴充套件的地方輕鬆的進行擴充套件。

以上這篇基於asp.net MVC 應用程式的生命週期(詳解)就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援指令碼之家。

您可能感興趣的文章:

驗證一個ASP.NET應用程式和頁面的生命週期的實現程式碼淺談ASP.NET MVC應用程式的安全性asp.net頁面生命週期詳解