[译]MVC应用程序生命周期

原文:MVC Application Lifecycleweb

来一探究竟在MVC应用程序中参与请求处理的各个不一样组件。浏览器

目录:架构

序言

在这篇文章中咱们将讨论MVC应用程序生成周期以及当请求从一个组件传到另外一个组件时是如何被处理的。咱们将说说这些在应用程序生命周期中依次发生的组件。咱们也将考虑每个组件的角色以及如何和管道中的其它组件相联系的。app

背景

做为开发人员,咱们都知道一些使用MVC框架来处理请求的组件。咱们大部分都是用控制器和操做方法来工做的。框架

咱们也使用不一样的ActionResult和View工做。可是,咱们知道其它参与请求处理的重要组件吗?知道在请求管道中请求是如何流动的吗?ide

当我开始学习MVC的时候,我不能理解的一件事情是请求是如何从一个组件流向另外一个组件。我也不清楚在请求处理过程当中HTTP模块的角色和HTTP处理程序。毕竟MVC是一个Web开发框架,因此它必须有HTTP模块和涉及管道中某些地方的HTTP处理程序。函数

在请求处理管道中咱们有更多的组件被涉及到,而后咱们知道,和咱们一块儿工做的控制器和操做方法在请求处理中有着一样重要的角色。学习

虽然大多数的时候咱们可使用该框架提供的默认功能,可是,若是咱们明白每个组件的角色,咱们能够很容易地互换组件或者提供咱们本身的自定义实现。测试

在请求管道中的主要组件以及它们的角色。ui

让咱们看看在一个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);
    }
}
View Code

 因此,如今咱们知道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 == 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
    }
View Code

这个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应用程序很是方便测试。咱们能够很容易地提供模拟对象来替代真实的对象,由于这里没有具体类的依赖。

相关文章
相关标签/搜索