首页 > 技术文章 > MVC学习笔记---MVC生命周期及管道

changrulin 2015-08-29 16:32 原文

ASP.NET和ASP.NET MVC的HttpApplication请求处理管道有共同的部分和不同之处,本系列将体验ASP.NET MVC请求处理管道生命周期的19个关键环节。

 

  ①以IIS6.0为例,首先由w3wp.exe维护着一个工作进程


1

 

  ②如果是第一次加载,由Aspnet_isapi.dll加载.NET运行时


2

 

  ③一个工作进程里有一个应用程序池,其中可以承载多个应用程序域AppDomain


3

 

  ④HTTP.SYS接收请求,通过应用程序域工厂AppDomainFactory创建应用程序域AppDomain


4

 

  ⑤一个IsapiRuntime被加载,并创建一个IsapiWorkerRequest对象封装当前的HTTP请求,并把该IsapiWorkerRequest对象传递给ASP.NET的HttpRuntime运行时,此时,  HTTP请求开始进入ASP.NET请求管道


5

 

也就是说,HttpRuntime是ASP.NET请求管道的入口。当请求进来,首先进入HttpRuntime,由HttpRuntime来决定如何处理请求。默认情况下,在machine.config和Web.config中并没有显式定义httpRuntime节点,但该节点是有默认值的,如下:

 

复制代码
<httpRuntime 
   apartmentThreading="false"
   appRequestQueueLimit="5000"
   delayNotificationTimeout="5"
   enable="true"
   enableHeaderChecking="true"
   enableKernelOutputCache="true"
   enableVersionHeader="true"
   encoderType = "System.Web.Util.HttpEncoder"
   executionTimeout="110"
   maxQueryStringLength = "2048"
   maxRequestLength="4096"
   maxUrlLength = "260"
   maxWaitChangeNotification="0"
   minFreeThreads="8"
   minLocalRequestFreeThreads="4"
   relaxedUrlToFileSystemMapping = "False"
   requestLengthDiskThreshold="80"
   requestPathInvalidCharacters = "<,>,*,%,&,:,\"
   requestValidationMode = "4.0"
   requestValidationType = "System.Web.Util.RequestValidator"
   requireRootedSaveAsPath="true"
   sendCacheControlHeader="true"
   shutdownTimeout="90"
   useFullyQualifiedRedirectUrl="false"
   waitChangeNotification="0" />
复制代码

通常,我们可以在Web.config中更改httpRuntime节点的默认值,如下:

复制代码
<configuration>
  <system.web>
  <httpRuntime maxRequestLength="4000"
    enable = "True"
    requestLengthDiskThreshold="512
    useFullyQualifiedRedirectUrl="True"
    executionTimeout="45"
    versionHeader="1.1.4128"/>
  </system.web>
</configuration>
复制代码

 

  ⑥HttpRuntime维护着一个HttpApplication池,当有HTTP请求过来,从池中选取可用的HttpApplication处理请求


6

 

HttpApplication有19个管道事件,分别是:

复制代码
1、BeginRequest:HTTP管道开始处理请求时,会触发BeginRequest事件
2、AuthenticateRequest:安全模块对请求进行身份验证时触发该事件
3、PostAuthenticateRequest:安全模块对请求进行身份验证后触发该事件
4、AuthorizeRequest:安全模块对请求进程授权时触发该事件
5、PostAuthorizeRequest:安全模块对请求进程授权后触发该事件
6、ResolveRequestCache:缓存模块利用缓存直接对请求进程响应时触发该事件
7、PostResolveRequestCache:缓存模块利用缓存直接对请求进程响应后触发该事件
8、PostMapRequestHandler:对于访问不同的资源类型,ASP.NET具有不同的HttpHandler对其进程处理。对于每个请求,ASP.NET会根据扩展名选择匹配相应的HttpHandler类型,成功匹配后触发该事件
9、AcquireRequestState:状态管理模块获取基于当前请求相应的状态(比如SessionState)时触发该事件
10、PostAcquireRequestState:状态管理模块获取基于当前请求相应的状态(比如SessionState)后触发该事件
11、PreRequestHandlerExecute:在实行HttpHandler前触发该事件
12、PostRequestHandlerExecute:在实行HttpHandler后触发该事件
13、ReleaseRequestState:状态管理模块释放基于当前请求相应的状态时触发该事件
14、PostReleaseRequestState:状态管理模块释放基于当前请求相应的状态后触发该事件
15、UpdateRequestCache:缓存模块将HttpHandler处理请求得到的相应保存到输出缓存时触发该事件
16、PostUpdateRequestCache:缓存模块将HttpHandler处理请求得到的相应保存到输出缓存后触发该事件
17、LogRequest:为当前请求进程日志记录时触发该事件
18、PostLogReques:为当前请求进程日志记录后触发该事件
19、EndRequest:整个请求处理完成后触发该事件
复制代码

我们可以在全局配置文件Global.asax中,按照约定的规则Application_{Event Name}来对管道事件定制:

 

  ⑦根据IsapiWorkerRequest对象,HttpRuntime创建HttpContext对象


7

 

  ⑧HttpApplicationFactory创建新的或者从HttpApplication池获取现有的、可用的HttpApplication对象

HttpApplication的工作包括:

● 初始化的时候加载全部的HttpModule
● 接收请求
● 在不同阶段引发不同的事件,使得HttpModule通过订阅事件的方式加入到请求的处理过程中
● 在一个特定阶段获取一个IHttpHandler实例,最终将请求交给具体的IHttpHandler来实现

8

 

  ⑨接下来,就是HttpModules发挥作用的时候


9


所有的HttpModules都实现了IHttpModule接口:

public interface IHttpModule
{
  void Init(HttpApplication app);
  void Dispose();
}

可见,HttoModules正是由Init方法,根据传入的HttpApplication类型参数,订阅了HttpApplication的所有事件。

我们自定义一个HttpModule:

复制代码
public class TestModule : IHttpModule
{
  public void Dispose(){}
  public void Init(HttpApplication app)
  {
    app.PostAcquireRequestState += new EventHandler(app_PostAcuiredRequestState);
    app.PreRequestHandlerExecute += new EventHandler(app_PreRequestHandlerExecute);
  }

  void app_PreRequestHandlerExecute(object sender, EventArgs e)
  {
    //TODO:
  }

  void app_PostAcquiredRequestState(object sender, EventArgs e)
  {
    //TODO:
  }
}
复制代码

 

  ⑩当某个请求与一个规则匹配后,ASP.NET会调用匹配的HttpHandlerFactory的GetHandler方法来获取一个HttpHandler实例, 最后由一个HttpHandler实例来处理当前请求,生成响应内容


10

所有的HttpHandlers都实现了IHttpHandler接口:

public interface IHttpHandler
{
  bool IsReusable{get;}
  void ProcessRequest(HttpContext context);
}

比如我们可以自定义一个HttpHandler来响应一类特定的请求:

复制代码
public class Login : IHttpHandler
{
  public void ProcessRequest(HttpContext context)
  {
    context.Response.ContentType = "text/plain";
    string username = context.Request.Form["name"];
    string password = context.Request.Form["password"];

    if(password="sth")
    {
      System.Web.Security.FormsAuthentication.SetAuthCookie(username, false);
      context.Response.Write("ok");
    }
    else
    {
      context.Response.Write("用户名和密码不正确");
    }
  }
}
复制代码

 

⑾ASP.NET MVC的入口在UrlRoutingModule,即订阅了HttpApplication的第7个管道事件PostResolveRequestCahce,换句话说,是在HtttpApplication的第7个管道事件处对请求进行了拦截


11

 

UrlRouteModlue实现了IHttpModule:

复制代码
public class UrlRoutingModule : IHttpModule
{
    // Fields
    private static readonly object _contextKey = new object();
    private static readonly object _requestDataKey = new object();
    private RouteCollection _routeCollection;
 
    // Methods
    protected virtual void Dispose()
    {
    }
 
    protected virtual void Init(HttpApplication application)
    {
        if (application.Context.Items[_contextKey] == null)
        {
            application.Context.Items[_contextKey] = _contextKey;
            application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
        }
    }
 
    private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
    {
        HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context);
        this.PostResolveRequestCache(context);
    }
 
    [Obsolete("This method is obsolete. Override the Init method to use the PostMapRequestHandler event.")]
    public virtual void PostMapRequestHandler(HttpContextBase context)
    {
    }
 
    public virtual void PostResolveRequestCache(HttpContextBase context)
    {
        RouteData routeData = this.RouteCollection.GetRouteData(context);
        if (routeData != null)
        {
            IRouteHandler routeHandler = routeData.RouteHandler;
            if (routeHandler == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
            }
            if (!(routeHandler is StopRoutingHandler))
            {
                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)
                {
                    if (!FormsAuthenticationModule.FormsAuthRequired)
                    {
                        throw new HttpException(0x191, SR.GetString("Assess_Denied_Description3"));
                    }
                    UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
                }
                else
                {
                    context.RemapHandler(httpHandler);
                }
            }
        }
    }
 
    void IHttpModule.Dispose()
    {
        this.Dispose();
    }
 
    void IHttpModule.Init(HttpApplication application)
    {
        this.Init(application);
    }
 
    // Properties
    public RouteCollection RouteCollection
    {
        get
        {
            if (this._routeCollection == null)
            {
                this._routeCollection = RouteTable.Routes;
            }
            return this._routeCollection;
        }
        set
        {
            this._routeCollection = value;
        }
    }
}
复制代码

UrlRoutingModule是在Web.config或默认的web.config中配置:

<httpModules>
            ......
            <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />
        </httpModules>

 

  ⑿而在请求到达UrlRoutingModule之前,我们在全局文件中做了如下配置

复制代码
public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            ......
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }

    public class RouteConfig
    {
        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 }
            );
        }
    }
复制代码

这意味着:在HttpApplication的第一个管道事件BeginRequest处,通过MapRoute()方法把路由注册到了RouteCollection中。在实际使用中,UrlRoutingModule是通过RouteTable的静态属性RouteCollection获取路由。

12

 

  ⒀当请求到达UrlRoutingModule的时候,UrlRoutingModule取出请求中的Controller、Action等RouteData信息,与路由表中的所有规则进行匹配,若匹配,把请求交给IRouteHandler,即MVCRouteHandler

13

 

MVCRouteHandler是用来生成实现IHttpHandler接口的MvcHandler:

复制代码
namespace System.Web.Routing
{  
    public interface IRouteHandler
    {       
        IHttpHandler GetHttpHandler(RequestContext requestContext);
    }
}
复制代码

UrlRoutingModule如何把请求交给MVCRouteHandler?
通过分析UrlRoutingModule的源码可以看到:

//通过RouteCollection的静态方法GetRouteData获取到封装路由信息的RouteData实例
RouteData routeData = this.RouteCollection.GetRouteData(context);

 

//再从RouteData中获取MVCRouteHandler
IRouteHandler routeHandler = routeData.RouteHandler;

 

为什么可以从RouteData中拿到MVCRouteHadnler呢?
因为当我们在HttpApplication的第一个管道事件,使用MapRoute()方法注册路由的时候,已经通过Route类的构造函数把MVCRouteHandler注入到路由中了。

复制代码
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) {
            if (routes == null) {
                throw new ArgumentNullException("routes");
            }
            if (url == null) {
                throw new ArgumentNullException("url");
            }

            Route route = new Route(url, new MvcRouteHandler()) {
                Defaults = new RouteValueDictionary(defaults),
                Constraints = new RouteValueDictionary(constraints),
                DataTokens = new RouteValueDictionary()
            };

            if ((namespaces != null) && (namespaces.Length > 0)) {
                route.DataTokens["Namespaces"] = namespaces;
            }

            routes.Add(name, route);

            return route;
        }
复制代码

 

  ⒁MVCRouteHandler把请求交给MvcHandler

 

还是从UrlRoutingModule的源码可以看到,通过HttpHandler的GetHttpHandler()方法获取到了实现了IHttpHandler接口的MVCHandler:

IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
context.RemapHandler(httpHandler);

14

 

MvcHandler的部分源码为:

复制代码
public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
{
        protected internal virtual void ProcessRequest(HttpContextBase httpContext)
        {
            SecurityUtil.ProcessInApplicationTrust(() =>
            {
                IController controller;
                IControllerFactory factory;
                ProcessRequestInit(httpContext, out controller, out factory);//初始化了ControllerFactory
                try
                {
                    controller.Execute(RequestContext);
                }
                finally
                {
                    factory.ReleaseController(controller);
                }
            });
        }

         private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) {
            bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(HttpContext.Current);
            if (isRequestValidationEnabled == true) {
                ValidationUtility.EnableDynamicValidation(HttpContext.Current);
            }
            AddVersionHeader(httpContext);
            RemoveOptionalRoutingParameters();
            string controllerName = RequestContext.RouteData.GetRequiredString("controller");
            factory = ControllerBuilder.GetControllerFactory();
            controller = factory.CreateController(RequestContext, controllerName);
            if (controller == null) {
              throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,MvcResources.ControllerBuilder_FactoryReturnedNull,factory.GetType(),controllerName));
            }
        }
}
复制代码

 

  ⒂从以上可以看出:首先通过ControllerBuilder的静态方法GetControllerFactory获取到实现IControllerFactory接口的ControllerFactory,然后根据从上下文中的路由数据中拿到controller名称,并据此创建实现IController接口的Controller

15

 

Controller派生于ControllerBase, 而ControllerBase实现了IController接口。ControllerBase的部分源码如下:

复制代码
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,都会执行基类ControllerBase的Execute()方法
● Execute()方法又会调用ExecuteCore()这个抽象方法
● ExecuteCore()这个抽象方法的实现被定义在Controller中
● 在Controller中的ExecuteCore()方法会调用ActionInvoker的InvokeAction()方法

 

  ⒃ActionInvoker激发Action方法

16

 

ActionInvoker实现了IActionInvoker接口:

public interface IActionInvoker
{
  bool InvokeAction(ControllerContext controllerContext, string actionName);
}

MVC默认的ActionInvoker是ControllerActionInvoker。

在Controller类中,提供了类型为IActionInvoker的属性ActionInvoker,当执行ExecuteCore()方法时会让这个ActionInvoker调用InvokeAction()方法激发Action。如下:

复制代码
public class Controller
{
  ......
  private IActionInvoker _actionInvoker;
  public IActionInvoker ActionInvoker
  {
    get
    {
      if(_actionInvoker == null)
      {
        _actionInvoker = CreateActionInvoker();
      }
      return _actionInvoker;
    }
    set
    {
      _actionInvoker = value;
    }
  }

  protected virtual IActionInvoker CreateActionInvoker()
  {
    return new ControllerActionInvoker();
  }

   public override void ExecuteCore()
   {
     ActionInvoker.InvokeAction(...);
   }
   .....
}
复制代码


ActionInvoker在执行InvokeAction()方法时会需要有关Controller和Action的相关信息,实际上,Controller信息(比如Controller的名称、类型、包含的Action等)被封装在ControllerDescriptor这个类中,Action信息(比如Action的名称、参数、属性、过滤器等)被封装在ActionDescriptor中。

 

另外,ActionDescriptor还提供了一个FindAction()方法,用来找到需要被执行的Action。

 

  ⒄ActionInvoker在执行InvokeAction()方法返回ActionResult

17

 

ActionResult是一个抽象类:

public abstract class ActionResult
{
  public abstract void ExecuteResult(ControllerContext context);
}

如果ActionResult是非ViewResult,比如JsonResult, ContentResult,这些内容将直接被输送到Response响应流中,显示给客户端;如果是ViewResult,就会进入下一个渲染视图环节。

 

  ⒅ViewEngine找到需要被渲染的视图

18

 

默认的有Razor View Engine和Web Form View Engine,实现IViewEngine接口。

 

IViewEngine接口方法:
● FindPartialView
● FindView
● ReleaseView

 

如果要创建自定义View Engine,只需要派生于VirtualPathProviderViewEngine这个类。

 

  ⒆View被加载成WebViewPage<TModel>类型,并渲染生成Html

19

 

调用ViewResult的ExecuteResult()方法,通过IView的Render()方法渲染成Html。

复制代码
public abstract class ViewResultBase : ActionResult
{
        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)
            {
                //通过视图引擎获取到ViewEngineResult ,此时模板页面【aspx】被加载成了WebViewPage<TModel>
                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);
            }
        }
}
复制代码

 

复制代码
void Application_Start(object sender, EventArgs e) {}
void Application_End(object sender, EventArgs e) {}
void Application_Error(object sender, EventArgs e) {}
void Session_Start(object sender, EventArgs e) {}
void Session_End(object sender, EventArgs e) {}
......

推荐阅读