首页 > 代码库 > [译]MVC应用程序生命周期
[译]MVC应用程序生命周期
原文:MVC Application Lifecycle
来一探究竟在MVC应用程序中参与请求处理的各个不同组件。
目录:
- 序言
- 背景
- UrlRoutingModule
- RouteHandler
- MvcHandler
- ControllerFactory
- Controller
- ActionInvoker
- ActionResult
- ViewEngine
- 总结
- 关注点
序言
在这篇文章中我们将讨论MVC应用程序生成周期以及当请求从一个组件传到另一个组件时是如何被处理的。我们将说说这些在应用程序生命周期中依次发生的组件。我们也将考虑每一个组件的角色以及如何和管道中的其它组件相联系的。
背景
作为开发人员,我们都知道一些使用MVC框架来处理请求的组件。我们大部分都是用控制器和操作方法来工作的。
我们也使用不同的ActionResult和View工作。但是,我们知道其它参与请求处理的重要组件吗?知道在请求管道中请求是如何流动的吗?
当我开始学习MVC的时候,我不能理解的一件事情是请求是如何从一个组件流向另一个组件。我也不清楚在请求处理过程中HTTP模块的角色和HTTP处理程序。毕竟MVC是一个Web开发框架,所以它必须有HTTP模块和涉及管道中某些地方的HTTP处理程序。
在请求处理管道中我们有更多的组件被涉及到,然后我们知道,和我们一起工作的控制器和操作方法在请求处理中有着同样重要的角色。
虽然大多数的时候我们可以使用该框架提供的默认功能,但是,如果我们明白每一个组件的角色,我们可以很容易地互换组件或者提供我们自己的自定义实现。
在请求管道中的主要组件以及它们的角色。
让我们看看在一个MVC应用程序中当我们如果第一次请求一个资源时发生了些什么。
UrlRoutingModule
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处理。
在这个阶段要发生的下面的事情。
- UrlRoutingModule附加路由处理到路由上
RouteHandler
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 =http://www.mamicode.com/= 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()方法中发生的事情如下:
- RouteCollection属性有一个GetRouteData()方法。这个GetRouteData()方法传递HttpContext对象为参数,并被调用。
- GetRouteData()方法返回RouteData对象routeData
- routeData有一个RouteHandler属性,它为当前请求返回IRouteHandler类型的MvcRouteHandler
- 这个MvcRouteHandler有一个GetHttpHandler()方法,该方法返回一个MvcHandler的引用
- 然后它将控制委托给了这个新的MvcHandler实例
MvcHandler
请求处理者
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()方法中接下来发生:
- ProcessRequestInit()方法被调用,从而创建了ControllerFactory
- 这个ControllerFactory创建了Controller
- Controller的Execute方法被调用
ControllerFactory
控制器的生成器
正如你所见,上面发生在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。
Controller
用户定义逻辑的容器
在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对象之后,下面发生的是:
- ControllerBase的Execute()方法被调用
- 这个Execute()方法调用声明为抽象并将由Controller类定义的ExecuteCore()方法
- Controller类的ExecuteCore()方法的实现会从RouteData中获取Action名称
- ExecuteCore()方法调用ActionInvoker的InvokeAction()方法
ActionInvoker
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()方法被调用时发生如下事情:
- ActionInvoker必须获取要执行的Controller和Action的信息。这个信息是由描述器对象提供的。Action和Controller描述器类的对象提供了Action和Controller的名字。
- Action方法被调用
ActionResult
命令对象
直到目前为止,正如我们所见,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所调用。
- 在Action方法被调用之后发生下面的事情:
- ActionFilter的OnActionExecuting方法被调用
- 之后,Action方法自身被调用
- 在Action方法被调用之后,ActionFilter的OnActionExecuted被调用
- ActionResult从Action方法被返回回来
- ActionResult的ExecuteResult()方法被调用
ViewEngine
视图的渲染器
ViewResult是最常见的被用在几乎所有应用程序中返回类型中的一个。使用ViewEngine被用来向客户端渲染视图。视图引擎负责从视图生成HTML。
当ViewResult被方法调用者调用时,它通过重写ExecuteResult方法向响应中渲染视图。
框架提供的视图引擎是Razor视图引擎和Web Form视图引擎。但是如果你需要一些自定义的某些定制功能的视图引擎的话,你可以通过实现所有的视图引擎都实现的IViewEngine接口来创建一个新的视图引擎。
IViewEngine有下面的几个方法:
- FindPartialView方法 当Controller希望根据所给的名字返回一个PartialView时,该方法被调用
- FindView方法 当Controller根据所给的名字查找一个View时,该方法被调用
- ReleaseView方法 这个方法被用来释放被ViewEngine所占住的资源
但是,并不用实现那些方法,创建视图引擎的一种简单方式是从一个新的抽象的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()方法被调用后发生的事情如下:
- ViewResultBase的ExecuteResult方法被调用
- ViewResultBase调用ViewResult的FindView
- ViewResult返回ViewEngineResult
- ViewEngineResult的Render方法被调用使用ViewEngine来渲染视图(?)
- 响应返回给客户端
总结
如果我们想要弄明白底层发生了什么的话,我们最好能够明白每一个组件的角色以及不同的组件是如何相互连接的。我们已经探究了由框架使用来处理响应的主要的接口和类。我相信这篇文章对于你理解MVC应用程序内部的细节是又很有帮助的。
关注点
有关MVC非常好的一点是那些所有的组件都是松耦合的。我们可以用另一个组件替换管道中的任意一个组件。这给了开发者很大的自由。这也意味着请求管道中的每一个阶段,我们都可以选择最合适的处理请求的组件。
我们也可以很自由地提供我们自己实现。这使应用程序更加可维护。
由于它松耦合架构,使得MVC应用程序非常方便测试。我们可以很容易地提供模拟对象来替代真实的对象,因为这里没有具体类的依赖。
[译]MVC应用程序生命周期