原文:MVC Application Lifecycleweb
來一探究竟在MVC應用程序中參與請求處理的各個不一樣組件。瀏覽器
目錄:架構
在這篇文章中咱們將討論MVC應用程序生成周期以及當請求從一個組件傳到另外一個組件時是如何被處理的。咱們將說說這些在應用程序生命週期中依次發生的組件。咱們也將考慮每個組件的角色以及如何和管道中的其它組件相聯繫的。app
做爲開發人員,咱們都知道一些使用MVC框架來處理請求的組件。咱們大部分都是用控制器和操做方法來工做的。框架
咱們也使用不一樣的ActionResult和View工做。可是,咱們知道其它參與請求處理的重要組件嗎?知道在請求管道中請求是如何流動的嗎?ide
當我開始學習MVC的時候,我不能理解的一件事情是請求是如何從一個組件流向另外一個組件。我也不清楚在請求處理過程當中HTTP模塊的角色和HTTP處理程序。畢竟MVC是一個Web開發框架,因此它必須有HTTP模塊和涉及管道中某些地方的HTTP處理程序。函數
在請求處理管道中咱們有更多的組件被涉及到,而後咱們知道,和咱們一塊兒工做的控制器和操做方法在請求處理中有着一樣重要的角色。學習
雖然大多數的時候咱們可使用該框架提供的默認功能,可是,若是咱們明白每個組件的角色,咱們能夠很容易地互換組件或者提供咱們本身的自定義實現。測試
在請求管道中的主要組件以及它們的角色。ui
讓咱們看看在一個MVC應用程序中當咱們若是第一次請求一個資源時發生了些什麼。
Mvc應用程序的入口
首先,請求被一個叫作UrlRoutingModule的HTTP模塊截獲。它是這種決定請求是否將由MVC應用程序處理的模塊。UrlRouting模塊選擇第一個匹配的路由。
UrlRoutingModule是如何將請求和應用程序中的路由匹配的呢?
若是你研究了由global.asax調用的RegisterRoutes方法的話,你將會注意到咱們添加路由到路由集合。這個方法是被global.asax中的Application_Start事件處理函數所調用的。
RegisterRoutes方法註冊了應用程序中的全部路由。
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); }
如今你可能會問UrlRouting模塊是如何知道它們的路由以及如何知道和這些路由相關聯的RouteHandler的呢?UrlRouting模塊使用MapRoute方法來知道它們的路由的。
若是你查看MapRoute方法,你將注意到它被定義爲一個擴展方法。
在後臺,它將RouteHandler和Route關聯了起來。MapRoute方法內部實現以下:
var Default = new Route(url, defaults , routeHandler);
因此本質來說這個方法作的事情就是將一個RouteHandler附加在一個路由上。
UrlRoutingModule被定義以下:
namespace System.Web.Routing { // 摘要: // 匹配定義的路由的 URL 請求。 [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")] public class UrlRoutingModule : IHttpModule { // 摘要: // 初始化 System.Web.Routing.UrlRoutingModule 類的新實例。 [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public UrlRoutingModule(); // 摘要: // 獲取或設置 ASP.NET 應用程序的定義路由的集合。 // // 返回結果: // 包含路由的對象。 public RouteCollection RouteCollection { get; set; } // 摘要: // 釋放模塊使用的資源(內存除外)。 protected virtual void Dispose(); // // 摘要: // 初始化模塊,並使其爲處理請求作好準備。 // // 參數: // application: // 一個對象,提供對 ASP.NET 應用程序中的全部應用程序對象的公用的方法、屬性和事件的訪問。 protected virtual void Init(HttpApplication application); // // 摘要: // 將當前請求的 HTTP 處理程序分配給上下文。 // // 參數: // context: // 封裝有關個別 HTTP 請求的全部 HTTP 特定的信息。 // // 異常: // System.InvalidOperationException: // 路由的 System.Web.Routing.RouteData.RouteHandler 屬性爲 null。 [Obsolete("This method is obsolete. Override the Init method to use the PostMapRequestHandler event.")] public virtual void PostMapRequestHandler(HttpContextBase context); // // 摘要: // 匹配路由的 HTTP 請求,檢索該路由的處理程序,並將該處理程序設置爲當前請求的 HTTP 處理程序。 // // 參數: // context: // 封裝有關個別 HTTP 請求的全部 HTTP 特定的信息。 public virtual void PostResolveRequestCache(HttpContextBase context); } }
因此,如今咱們知道UrlRoutingModule知道應用程序中的全部路由,所以它能夠爲請求匹配正確的路由。這裏主要須要注意的事情是,UrlRoutingModule選擇第一個匹配的路由。一旦一個匹配被髮如今路由表中,掃描過程就會中止。
因此咱們能夠說,在咱們的應用程序中咱們有十個路由,越是明確的路由越是要定義在廣泛路由以前,這樣以防稍後添加的明確路由由於廣泛路由被匹配而將不能被匹配。所以咱們須要關心何時添加路由到路由集合中。
這裏,若是請求被路由集合中的任意一個路由匹配的話,在集合中添加稍晚的其它路由將不能處理請求。請注意,若是請求不能被集合中的任意一個路由匹配的話,它將不能被MvcApplication處理。
在這個階段要發生的下面的事情。
MvcHandler的生成器
正如咱們已經看到的那樣,MvcRouteHandler實例使用MapRoute方法附加在路由上。MvcRouteHandler實現了IRouteHandler接口。
這個MvcRouteHandler對象被用於爲咱們的應用程序獲取一個叫作MvcHandler的HttpHandler。
當MvcRouteHandler被建立的時候,它作的事情之一就是調用PostResolveRequestCache()方法。PostResolveRequestCache()方法被定義以下:
public virtual void PostResolveRequestCache(HttpContextBase context) { RouteData routeData = this.RouteCollection.GetRouteData(context); if (routeData == null) { return; } 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) { return; } 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)) { context.RemapHandler(httpHandler); return; } if (FormsAuthenticationModule.FormsAuthRequired) { UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this); return; } throw new HttpException(401, SR.GetString("Assess_Denied_Description3")); }
在PostResolveRequestCache()方法中發生的事情以下:
請求處理者
MvcHandler被定義爲:
正如你所見,它是一個標準的Http Handler。做爲一個Http Handler,它實現了ProcessRequest()方法。這個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); } }); }
正如你所見,上面的ProcessRequest()方法調用了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)); } }
在ProcessRequest()方法中接下來發生:
控制器的生成器
正如你所見,上面發生在ProcessRequest()方法內部的事情之一是獲取用來建立控制器對象的ControllerFactory。Controller Factory實現了IControllerFactory接口。
當用ControllerBuilder建立ControllerFactory時,框架默認建立的是DefaultControllerFactory類型。
ControllerBuilder是一個單例類,被用來建立ControllerFactory。下面一行就是在ProcessRequestInit()方法中建立ControllerFactory.
factory = ControllerBuilder.GetControllerFactory();
這樣GetControllerFactory()方法返回了ControllerFactory對象。因此如今咱們就有了ControllerFactory對象。
ControllerFactory使用CreateController方法來建立Controller。CreateController被定義爲:
IController CreateController( RequestContext requestContext, string controllerName )
使用默認的ControllerFactory實現來建立ControllerBase對象。
若是有必要的話,咱們能夠經過實現IControllerFactory接口來擴展這個工廠,而後在global.asax的Application_Start事件中作以下聲明:
ControllerBuilder.Current.SetDefaultControllerFactory(typeof(NewFactory));
這個SetControllerFactory()方法被用來設置自定義Controller Factory,用以代替由框架使用的默認Controller Factory。
用戶定義邏輯的容器
在MvcHandler中的ProcessRequest()方法中,咱們已經看到ControllerFactory建立了Controller對象。
衆所周知,Controller包含着Action方法。當在瀏覽器中請求一個URL時,一個Action方法獲得調用。咱們使用爲咱們提供不少功能的Controller類,而不是顯式地實現IController來建立咱們的Controller。
如今這個Controller類是繼承自另外一個叫作ControllerBase的類,該類被定義以下:
public abstract class ControllerBase : IController { private readonly SingleEntryGate _executeWasCalledGate = new SingleEntryGate(); private DynamicViewDataDictionary _dynamicViewDataDictionary; private TempDataDictionary _tempDataDictionary; private bool _validateRequest = true; private IValueProvider _valueProvider; private ViewDataDictionary _viewDataDictionary; public ControllerContext ControllerContext { get; set; } [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This property is settable so that unit tests can provide mock implementations.")] public TempDataDictionary TempData { get { if (ControllerContext != null && ControllerContext.IsChildAction) { return ControllerContext.ParentActionViewContext.TempData; } if (_tempDataDictionary == null) { _tempDataDictionary = new TempDataDictionary(); } return _tempDataDictionary; } set { _tempDataDictionary = value; } } public bool ValidateRequest { get { return _validateRequest; } set { _validateRequest = value; } } public IValueProvider ValueProvider { get { if (_valueProvider == null) { _valueProvider = ValueProviderFactories.Factories.GetValueProvider(ControllerContext); } return _valueProvider; } set { _valueProvider = value; } } public dynamic ViewBag { get { if (_dynamicViewDataDictionary == null) { _dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData); } return _dynamicViewDataDictionary; } } [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This property is settable so that unit tests can provide mock implementations.")] public ViewDataDictionary ViewData { get { if (_viewDataDictionary == null) { _viewDataDictionary = new ViewDataDictionary(); } return _viewDataDictionary; } set { _viewDataDictionary = value; } } 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(); protected virtual void Initialize(RequestContext requestContext) { ControllerContext = new ControllerContext(requestContext, this); } internal void VerifyExecuteCalledOnce() { if (!_executeWasCalledGate.TryEnter()) { string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType()); throw new InvalidOperationException(message); } } #region IController Members void IController.Execute(RequestContext requestContext) { Execute(requestContext); } #endregion }
這個Controller對象使用ActionInvoker來調用Controller中的Action方法,稍後咱們來仔細看看。
使用Controller Factory建立Controller對象以後,下面發生的是:
Action選擇器
ActionInvoker類有一些發現Controller中Action方法並調用這個Action方法最重要的職責。
ActionInvoker是一個實現了IActionInvoker接口類型的對象。
bool InvokeAction(ControllerContext controllerContext, string actionName);
Controller類提供了IActionInvoker的默認實現,爲ControllerActionInvoker。
Controller類暴露一個返回ControllerActionInvoker類型名叫ActionInvoker的屬性。它使用CreateActionInvoker()方法來建立ControllerActionInvoker實例。正如你所見,這個方法被定義爲了虛方法,因此咱們能夠重寫它並提供咱們本身的返回自定義的ActionInvoker的實現。
public IActionInvoker ActionInvoker { get { if (_actionInvoker == null) { _actionInvoker = CreateActionInvoker(); } return _actionInvoker; } set { _actionInvoker = value; } } protected virtual IActionInvoker CreateActionInvoker() { return new ControllerActionInvoker(); }
ActionInvoker類須要得到要執行的Action方法和Controller的明細,這些明細是由ControllerDescriptor提供的。ControllerDescriptor和ActionDescriptor在ActionInvoker中扮演着重要的做用。
ControllerDescriptor被定義爲"封裝描述着Controller的信息,例如它的名字,類型和Actions等"。
ActionDescriptor被定義爲"提供一個Action方法的信息,例如它的名字,Controller,參數,特性和過濾器等"。
ActionDescriptor的一個重要方法是"FindAction()"。這個方法返回一個表明着將要執行的Action的ActionDescriptor對象。因此,ActionInvoker知道要調用哪個Action。
正如咱們所見,上面的ActionInvoker的InvokeAction()方法在ExecuteCore()中被調用。
當ActionInvoker的InvokeAction()方法被調用時發生以下事情:
命令對象
直到目前爲止,正如咱們所見,Action方法被ActionInvoker調用。
Action方法的特色之一是它一直返回ActionResult類型,而不會返回不一樣的數據類型。ActionResult是一個抽象類,被定義以下:
public abstract class ActionResult { public abstract void ExecuteResult(ControllerContext context); }
由於ExecuteResult()是一個抽象方法,因此不一樣的子類可提供ExecuteResult()方法的不一樣實現。
須要注意的一個重要的事情是,一個Action的結果表示該框架表明操做方法執行命令。衆所周知,Action方法包含執行邏輯,Action結果返回給客戶端。Action方法它們自己僅僅返回ActionResult可是不執行它。
ActionResult被執行,響應返回給客戶端。因此,ActionResult對象表示在整個方法中能被傳遞的結果。
所以它將規範和實現分割開來,由於它表明着命令對象。爲了理解.NET中命令,請參考commands
這裏有一些特定的依賴咱們想要返回的結果類型的ActionResult類,例如Json或Redirect到另外一個方法。
咱們使用的繼承自咱們Controller類的"Controller"類提供了許多很方便地咱們能夠用的有用的功能。
它提供的這樣的一個功能,就是返回特定的ActionResult類型的方法。因此,咱們能夠僅僅調用它們的方法而不用明確地建立ActionResult對象。
下面是一些ActionResult類型和它們對應的返回ActionResult的方法:
ActionResult Class | Helper Method | ReturnType |
ViewResult | View | web page |
JsonResult | Json | Retuns a serialized JSON object |
RedirectResult | Redirect | Redirects to another action method |
ContentResult | Content | Returns a user-defined content type |
直到如今,咱們已經看到Action方法被ActionInvoker所調用。
視圖的渲染器
ViewResult是最多見的被用在幾乎全部應用程序中返回類型中的一個。使用ViewEngine被用來向客戶端渲染視圖。視圖引擎負責從視圖生成HTML。
當ViewResult被方法調用者調用時,它經過重寫ExecuteResult方法向響應中渲染視圖。
框架提供的視圖引擎是Razor視圖引擎和Web Form視圖引擎。可是若是你須要一些自定義的某些定製功能的視圖引擎的話,你能夠經過實現全部的視圖引擎都實現的IViewEngine接口來建立一個新的視圖引擎。
IViewEngine有下面的幾個方法:
可是,並不用實現那些方法,建立視圖引擎的一種簡單方式是從一個新的抽象的VitualPathProviderViewEngine類派生。這個類處理了像找視圖這樣底層的細節。
正如上所見,ActionResult被調用了。既然ViewResult是最普通的ActionResult類型,若是ViewResult中ExecuteResult()方法被調用的時候咱們來探究一下到底發生了什麼。
這兒有兩格重要的類,ViewResultBase和ViewResult。ViewResultBase包含下面調用ViewResult中FindView方法的代碼。
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); 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); } } protected abstract ViewEngineResult FindView(ControllerContext context);
protected override ViewEngineResult FindView(ControllerContext context) { ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName); if (result.View != null) { return result; } // we need to generate an exception containing all the locations we searched StringBuilder locationsText = new StringBuilder(); foreach (string location in result.SearchedLocations) { locationsText.AppendLine(); locationsText.Append(location); } throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.Common_ViewNotFound, ViewName, locationsText)); }
ExecuteResult()方法被調用後發生的事情以下:
若是咱們想要弄明白底層發生了什麼的話,咱們最好可以明白每個組件的角色以及不一樣的組件是如何相互鏈接的。咱們已經探究了由框架使用來處理響應的主要的接口和類。我相信這篇文章對於你理解MVC應用程序內部的細節是又頗有幫助的。
有關MVC很是好的一點是那些全部的組件都是鬆耦合的。咱們能夠用另外一個組件替換管道中的任意一個組件。這給了開發者很大的自由。這也意味着請求管道中的每個階段,咱們均可以選擇最合適的處理請求的組件。
咱們也能夠很自由地提供咱們本身實現。這使應用程序更加可維護。
因爲它鬆耦合架構,使得MVC應用程序很是方便測試。咱們能夠很容易地提供模擬對象來替代真實的對象,由於這裏沒有具體類的依賴。