MVC 应用程序生命周期






4.83/5 (103投票s)
深入了解 MVC 应用程序请求处理中涉及的不同组件。
目录
- 引言
- 背景
- UrlRoutingModule
- RouteHandler
- MvcHandler
- ControllerFactory
- 控制器 (Controller)
- ActionInvoker
- ActionResult
- ViewEngine
在本文中,我们将讨论 MVC 应用程序的生命周期以及请求在从一个组件传递到另一个组件的过程中是如何被处理的。我们将按照它们在应用程序生命周期中出现的顺序来讨论这些组件。我们还将探讨每个组件的作用以及它们与管道中其他组件的关系。
背景
作为开发者,我们了解 MVC 框架用于处理请求的一些组件。我们主要处理控制器和操作方法。
我们还使用不同的 ActionResult 和 Views 。但我们是否了解请求处理中涉及的其他重要组件?以及请求在请求管道中如何流动?
当我开始学习 MVC 时,我无法理解的一件事是如何请求从一个组件流向另一个组件。另外,我也不清楚 HTTP 模块和 HTTP 处理程序在请求处理中的作用。毕竟 MVC 是一个 Web 开发框架,所以管道中一定会有 HTTP 模块和 HTTP 处理程序的参与。
与我们所知道的相比,这个请求处理管道中涉及的组件更多,我们所使用的控制器和操作方法,在请求处理中也起着同等重要的作用。
虽然我们大部分时间都可以使用框架提供的默认功能,但如果我们了解每个组件的作用,我们就可以轻松地替换组件或提供自己的自定义实现。
请求管道中的主要组件及其作用。
让我们看看当 MVC 应用程序中一个资源的请求首次发出时会发生什么
MVC 应用程序的入口
请求首先被 UrlRoutingModule 拦截,这是一个 HTTP 模块。 正是这个模块决定了请求是否会被我们的 MVC 应用程序处理。 UrlRouting Module 选择第一个匹配的路由。
UrlRoutingModule 如何将请求与应用程序中的路由匹配?
如果您查看从 global.asax 调用 的 RegisterRoutes 方法,您会注意到我们将路由添加到 routes RouteCollection 中。此方法从 global.asax 的 application_start 事件处理程序调用
正是 RegisterRoutes 方法注册了应用程序中的所有路由
RouteConfig.RegisterRoutes(RouteTable.Routes);
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 }
);
}
现在您可能会问, UrlRouting Module 是如何知道这些路由的,以及路由是如何与 RouteHandler 关联的? UrlRouting Module 使用 maproute 方法 知道这些路由。
如果您查看 maproute 方法,您会注意到它被定义为一个扩展方法。
在后台,它将 routeHandler 与路由关联起来。MapRoute 方法的内部实现如下:
var Default = new Route(url, defaults , routeHandler);
所以基本上这个方法的作用是将一个 routeHandler 附加到路由上。
UrlRoutingModule 定义如下
public class UrlRoutingModule : IHttpModule
{
public UrlRoutingModule();
public RouteCollection RouteCollection { get; set; } //omitting the other details
因此,现在我们知道 UrlRoutingModule 了解应用程序中的所有路由,因此它可以为请求匹配正确的路由。要点是 UrlRoutingModule 选择第一个匹配的路由。一旦在路由表中找到匹配项,扫描过程就会停止。
所以,假设我们的应用程序中有 10 个路由,并且更具体的路由定义在更通用的路由之后,那么在这种情况下,后面添加的特定路由将永远不会被匹配,因为更通用的路由总是会被匹配。因此,我们在将路由添加到路由集合时需要注意这一点。
如果请求被路由集合中的任何一个路由匹配,那么添加到集合中的其他路由将无法处理请求。请注意,如果请求未被 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 != null) {
IRouteHandler routeHandler = routeData.RouteHandler;
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
在 PostResolveRequestCache() 方法中发生以下情况。
- RouteCollection 属性有一个 GetRouteData() 方法。调用此GetRouteData() 方法并将 HttpContext 传递给它。
- GetRouteData() 方法返回 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
Controller 的生成器
如上所示,ProcessRequest() 方法中的一个操作是获取用于创建 Controller 对象的 ControllerFactory。Controller factory 实现接口 IControllerFactory。
默认情况下,当 ControllerFactory 通过 ControllerBuilder 创建时,框架会创建 DefaultControllerFactory 类型。
ControllerBuilder 是一个单例类,用于创建 ControllerFactory。ProcessRequestInit() 方法中的以下行创建了 ControllerFactory。
factory = ControllerBuilder.GetControllerFactory();
因此, GetControllerFactory() 方法返回 ControllerFactory 对象。所以现在我们有了 ControllerFactory 对象。
ControllerFactory 使用 CreateController 方法创建控制器。CreateController 定义如下
IController CreateController(
RequestContext requestContext,
string controllerName )
使用默认的 ControllerFactory 实现创建 ControllerBase 对象。
如果需要,我们可以通过实现 IControllerFactory 接口来扩展,然后在 global.asax 的 Application_Start 事件中声明以下内容。
ControllerBuilder.Current.SetDefaultControllerFactory(typeof(NewFactory))
SetControllerFactory() 方法用于设置 自定义 controller factory 而不是框架使用的默认 Controller Factory。
用户定义逻辑的容器
因此,我们已经看到 ControllerFactory 在 ProcessRequest() 方法中创建了 Controller 对象,该方法位于 MvcHandler 中。
如我们所知,控制器包含操作方法。当我们请求浏览器中的 URL 时,就会调用一个操作方法。与其显式实现 IController 接口,不如我们使用 Controller 类来创建我们的控制器,该类为我们提供了许多功能。
现在这个 Controller 类继承自另一个名为 "ControllerBase" 的 Controller 类,定义如下
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 来调用控制器中的操作方法,我们稍后将对此进行介绍。
使用控制器工厂创建控制器对象后,将发生以下情况:
- 调用 ControllerBase 的 Execute() 方法
- 此 Execute() 方法调用 ExecuteCore() 方法,该方法声明为抽象方法,由 Controller 类定义。
- Controller 类的 ExecuteCore() 方法实现从 RouteData 检索操作名称
- ExecuteCore() 方法调用 ActionInvoker 的 InvokeAction() 方法。
ActionInvoker
操作选择器
ActionInvoker 类承担一些最重要的职责,例如查找控制器中的操作方法,然后调用该操作方法。
ActionInvoker 是一个实现 IActionInvoker 接口的类型的对象。The IActionInvoker 接口定义了一个单一方法:
bool InvokeAction(
ControllerContext controllerContext,
string actionName
)
Controller 类提供了 IActionInvoker 的默认实现,即ControllerActionInvoker
Controller 类公开了一个名为 ActionInvoker 的属性,该属性返回 which returns the ControllerActionInvoker 。它使用 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 类需要获取要执行的操作方法和控制器的详细信息。这些详细信息由 ControllerDescriptor 提供。ControllerDescriptor 和 ActionDescriptor 在 ActionInvoker 中扮演着重要角色。
ControllerDescriptor 定义为:"封装描述控制器的信息,例如其名称、类型和操作"。
ActionDescriptor 定义为:"提供有关操作方法的信息,例如其名称、控制器、参数、属性和筛选器"。
ActionDescriptor 的一个重要方法是 "FindAction()"。此方法返回一个代表要执行的操作的 ActionDescriptor 对象。因此 ActionInvoker 知道要调用哪个操作。
如上所述,ActionInvoker 的 InvokeAction() 方法在 ExecuteCore() 方法中被调用。
调用 ActionInvoker 的 InvokeAction() 方法时会发生以下情况
- ActionInvoker 必须获取有关控制器和要执行的操作的信息。这些信息由描述符对象提供。操作和控制器描述符类提供了控制器和操作的名称。
- ActionMethod 被调用
ActionResult
命令对象
因此,到目前为止,我们已经看到 ActionMethod 被 ActionInvoker 调用。
操作方法的一个特点是,它总是返回 ActionResult 类型,而不是返回不同的数据类型。ActionResult 是一个抽象类,定义如下
public abstract class ActionResult
{
public abstract void ExecuteResult(ControllerContext context);
}
由于 ExecuteResult() 是一个抽象方法,因此不同的子类提供了 ExecuteResult() 方法的不同实现。
要记住的一个重要一点是,ActionResult 代表框架代表操作方法执行的命令。如我们所知,ActionMethods 包含要执行的逻辑,并将结果返回给客户端。操作方法本身只返回 ActionResult,而不执行它。
此 ActionResult 被执行,并将响应返回给客户端。因此,ActionResult对象代表可以跨方法传递的结果。
因此,它将规范与实现分离,因为它代表命令对象。要理解 .NET 中的命令,请参考命令。
根据我们想要返回的结果类型,有特定的 ActionResult 类,例如 Json 或重定向到另一个方法。
我们用来继承控制器类的"Controller"类提供了许多开箱即用的有用功能。
其中一项功能是返回特定类型 ActionResult 的方法。因此,与其显式创建 ActionResult 对象,不如直接调用这些方法。
以下是一些方法及其返回的 ActionResult 类型
ActionResult 类 | 辅助方法 | 返回类型 |
ViewResult | View | 网页 |
JsonResult | Json | 返回序列化的 JSON 对象 |
RedirectResult | Redirect | 重定向到另一个操作方法 |
ContentResult | Content | 返回用户定义的 MIME 类型 |
因此,到目前为止,我们已经看到 ActionMethod 被 ActionInvoker 调用
调用操作方法后发生以下情况。
- 调用 ActionFilters 的 OnActionExecuting 方法。
- 之后,调用操作方法本身。
- 调用操作方法后,调用 ActionFilters 的 OnActionExecuted 方法。
- ActionResult 从 ActionMethod 返回
- 调用 ActionResult 的 ExecuteResult() 方法。
ViewEngine
视图的渲染器
ViewResult 是几乎所有应用程序中最常见的返回类型之一。它用于使用 ViewEngine 将视图渲染到客户端。 View Engine 负责从视图生成 HTML
当 Action Invoker 调用 ViewResult 时,它通过覆盖 ExecuteResult 方法将视图渲染到响应中。
框架提供的 View Engines 是 Razor View Engine 和 Web Form View Engine。但如果您需要自定义 View Engine 来实现一些自定义功能,您可以创建一个新的 View Engine,通过实现 IViewEngine 接口,该接口是所有 View Engines 都实现的。
IViewEngine 具有以下方法:
- FindPartialView 调用 FindPartialView 方法是在 Controller 查找要返回的具有给定名称的 Partial View 时。
- FindView The FindView 方法在 Controller 查找具有给定名称的 View 时被调用。
- ReleaseView 方法用于释放 ViewEngine 持有的资源。
但与其实现这些方法,不如创建一个视图引擎的更简单方法是派生自抽象 "VirtualPathProviderViewEngine" 类的类。此类处理查找视图等底层细节。
因此,我们已经看到 ActionResult 的调用。由于 ViewResult 是最常见的 ActionResult 类型,我们将探讨如果调用 ViewResult 的 ExecuteResult() 方法会发生什么。
有两个重要的类 ViewResultBase 和 ViewResult。ViewResultBase 包含以下代码,该代码调用 ViewResult 中的 FindViewMethod
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() 方法以使用 ViewEngine 渲染视图。
- 响应被返回给客户端。
摘要
如果我们理解了幕后发生的事情,我们就能更好地理解每个组件的作用以及不同组件是如何相互连接的。我们研究了框架用于处理响应的一些主要接口和类。我相信本文能帮助您理解 MVC 应用程序的内部细节。
关注点
MVC 的一个优点是所有这些组件都是松耦合的。We 可以用另一个组件替换管道中的任何组件。这为开发人员提供了很大的自由度。这意味着在请求管道的每个阶段,我们都可以选择合适的组件来处理请求。
我们也可以自由地提供自己的实现。这使得应用程序更易于维护。
正是这种松耦合的架构使得 MVC 应用程序适合测试。由于没有具体的类依赖关系,我们可以轻松地用模拟对象替换实际对象。
MVC 相关概念
历史
2014 年 4 月 4 日 当日文章