客戶端發送請求->IIS, UrlRouting模塊對比URL, 預設如果該URL能對應到實體文件則退出MVC管道把控制權交還給IIS.如果RegisterRoutes中的路由規則對比成功預設情況下交給MvcRouteHandler(IRouteHandler)處理,IRouteHandler的作用...
客戶端發送請求->IIS, UrlRouting模塊對比URL, 預設如果該URL能對應到實體文件則退出MVC管道把控制權交還給IIS.
如果RegisterRoutes中的路由規則對比成功預設情況下交給MvcRouteHandler(IRouteHandler)處理, IRouteHandler的作用是決策使用哪一個HttpHandler處理本次請求,IRouteHandler介面定義如下:
[TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")] public interface IRouteHandler { IHttpHandler GetHttpHandler(RequestContext requestContext); }
微軟的提供的MVC框架中MvcRouteHandler實現了IRouteHandler介面,預設交給MvcHandler處理,代碼如下:
public class MvcRouteHandler : IRouteHandler { private IControllerFactory _controllerFactory; 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); } }
As we know, 所有的HttpHandler的入口點為ProcessRequest,IHttpHandler介面定義如下:
public interface IHttpHandler { void ProcessRequest(HttpContext context); bool IsReusable { get; } }
在MvcRouteHandler.ProcessRequest中首先傳入HttpContext以及兩個out參數到ProcessRequestInit方法中根據路由參數獲取ControllerFactory類以及對應的Controller, 代碼如下:
protected internal virtual void ProcessRequest(HttpContextBase httpContext) { IController controller; IControllerFactory factory; this.ProcessRequestInit(httpContext, out controller, out factory); try { controller.Execute(this.RequestContext); } finally { factory.ReleaseController(controller); } } private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) { HttpContext current = HttpContext.Current; if ((current != null) && (ValidationUtility.IsValidationEnabled(current) == true)) { ValidationUtility.EnableDynamicValidation(current); } this.AddVersionHeader(httpContext); this.RemoveOptionalRoutingParameters(); string requiredString = this.RequestContext.RouteData.GetRequiredString("controller"); factory = this.ControllerBuilder.GetControllerFactory(); controller = factory.CreateController(this.RequestContext, requiredString); if (controller == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[] { factory.GetType(), requiredString })); } }
在Asp.net Mvc中所有的Controller都實現了IController介面,該介面定義了一個Execute方法,代碼如下:
public interface IController { void Execute(RequestContext requestContext); }
微軟提供的預設框架中實現了該介面的Class為ControllerBase, ControllerBase除了實現Execute方法外還定義了一個抽象方法ExecuteCore,而實現了這個方法的就是我們工作中定義Controller時繼承需要繼承的System.Web.MvcController類,接上文講,在MvcRouteHandler.ProcessRequest中取得Controller後接著會進入ControllerBase的Execute方法,代碼如下:
protected virtual void Execute(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (requestContext.HttpContext == null) { throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext"); } this.VerifyExecuteCalledOnce(); this.Initialize(requestContext); using (ScopeStorage.CreateTransientScope()) { this.ExecuteCore(); } }
在做了一些驗證和初始化後會進入System.Web.MvcController類的ExecuteCore方法,代碼如下:
protected override void ExecuteCore() { this.PossiblyLoadTempData(); try { string requiredString = this.RouteData.GetRequiredString("action"); if (!this.ActionInvoker.InvokeAction(base.ControllerContext, requiredString)) { this.HandleUnknownAction(requiredString); } } finally { this.PossiblySaveTempData(); } }
該方法中會通過路由參數得知要運行的ActionName並通過ActionInvoker調用該Action響應客戶端, 到此為止就是我們平時經常使用的Action所做的事情了,如果直接用Response客戶端或返回數據本次請求的生命周期則到此結束.
若返回ViewResult還有有一些額外的操作, 通過ViewEngine獲取到相應的View返回給客戶端,我們先看一下IViewEngine介面的定義:
public interface IViewEngine { ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache); ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache); void ReleaseView(ControllerContext controllerContext, IView view); }
.Net內建了兩套ViewEngine,一套為WebFormViewEngine,另一套為我們比較常用的RazorViewEngine,代碼如下:
public class RazorViewEngine : BuildManagerViewEngine { internal static readonly string ViewStartFileName = "_ViewStart"; public RazorViewEngine() : this(null) { } public RazorViewEngine(IViewPageActivator viewPageActivator) : base(viewPageActivator) { base.AreaViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; base.AreaMasterLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; base.AreaPartialViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; base.MasterLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; base.PartialViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; base.FileExtensions = new string[] { "cshtml", "vbhtml" }; } protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { string layoutPath = null; bool runViewStartPages = false; IEnumerable<string> fileExtensions = base.FileExtensions; return new RazorView(controllerContext, partialPath, layoutPath, runViewStartPages, fileExtensions, base.ViewPageActivator) { DisplayModeProvider = base.DisplayModeProvider }; } protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) { string layoutPath = masterPath; bool runViewStartPages = true; IEnumerable<string> fileExtensions = base.FileExtensions; return new RazorView(controllerContext, viewPath, layoutPath, runViewStartPages, fileExtensions, base.ViewPageActivator) { DisplayModeProvider = base.DisplayModeProvider }; } }
可以看到在ViewEngine中已經內建了很多找到某個具體View文件的格式,這也是為什麼我們如果不自定義MVC框架的某些模塊必須嚴格按照微軟提供的目錄結構來分割文件。
Tip: 這實際上是有好處的,也是微軟提倡的Convention Over Configuration(覺得沒法有一個簡單詞概括所以就不翻譯了), 意思就是他提供了一個標準的模式,大家都遵守這樣一個規則,以降低不同的人維護同一個項目的複雜度: 我們知道如果每個團隊甚至每個人寫出的項目如果都是不同的目錄結構與項目框架,如果臨時換另一個團隊或另一個人來接手是需要花很多時間來熟悉的, 項目越複雜也就越難以維護。
接著通過ViewEngine通過CreateView實例化一個IView對象並返回,IView介面定義如下:
public interface IView { void Render(ViewContext viewContext, TextWriter writer); }
顯然就是Render用於響應客戶端的方法了,在RazorViewEngine中將會返回一個實現了IView介面的RazorView對象並調用Render方法最終輸出頁面到客戶端:
protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance) { if (writer == null) { throw new ArgumentNullException("writer"); } WebViewPage page = instance as WebViewPage; if (page == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.CshtmlView_WrongViewBase, new object[] { base.ViewPath })); } page.OverridenLayoutPath = this.LayoutPath; page.VirtualPath = base.ViewPath; page.ViewContext = viewContext; page.ViewData = viewContext.ViewData; page.InitHelpers(); if (this.VirtualPathFactory != null) { page.VirtualPathFactory = this.VirtualPathFactory; } if (this.DisplayModeProvider != null) { page.DisplayModeProvider = this.DisplayModeProvider; } WebPageRenderingBase startPage = null; if (this.RunViewStartPages) { startPage = this.StartPageLookup(page, RazorViewEngine.ViewStartFileName, this.ViewStartFileExtensions); } HttpContextBase httpContext = viewContext.HttpContext; WebPageRenderingBase base4 = null; object model = null; page.ExecutePageHierarchy(new WebPageContext(httpContext, base4, model), writer, startPage); }
至此完整的生命周期介紹完畢,實際上關於後面的ViewEngine部分只是粗淺的介紹了一下運轉流程,關於具體細節需要更大的篇幅來介紹在此就不再展開,目前除了微軟內建的ViewEngine外, 也有很多優秀的第三方ViewEngine可供大家參考比如SparkViewEngine、NDjango、NHaml等.
由於個人水平有限,理解有誤的地方懇請斧正!