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

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

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

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

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

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

也就是说,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处理请求

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对象

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

HttpApplication的工作包括:

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

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

所有的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实例来处理当前请求,生成响应内容

所有的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个管道事件处对请求进行了拦截

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获取路由。

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

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);

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

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方法

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

ActionResult是一个抽象类:

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

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

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

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

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

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

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

调用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) {}
......

MVC学习笔记---MVC生命周期及管道的更多相关文章

  1. MVC学习笔记---MVC生命周期

    Asp.net应用程序管道处理用户请求时特别强调"时机",对Asp.net生命周期的了解多少直接影响我们写页面和控件的效率.因此在2007年和2008年我在这个话题上各写了一篇文章 ...

  2. MVC学习笔记---MVC的处理管线

    漫步ASP.NET MVC的处理管线   ASP.NET MVC从诞生到现在已经好几个年头了,这个框架提供一种全新的开发模式,更符合web开发本质.你可以很好的使用以及个性化和扩展这个框架,但这需要你 ...

  3. Angular 5.x 学习笔记(2) - 生命周期钩子 - 暂时搁浅

    Angular 5.x Lifecycle Hooks Learn Note Angular 5.x 生命周期钩子学习笔记 标签(空格分隔): Angular Note on cnblogs.com ...

  4. Java Web学习笔记-Servle生命周期

    Servlet会在服务器启动或第一次请求该Servlet的时候开始生命周期,在服务器停止的时候结束生命周期. 无论请求多少次Servlet,最多只有一个Servlet实例.多个客户端并发请求Servl ...

  5. android学习笔记 activity生命周期&任务栈&activity启动模式

    activity生命周期 完整生命周期 oncreate->onstart->onresume->onpause->onstop->ondestory 使用场景:应用程序 ...

  6. iOS学习笔记—ViewController/生命周期

    ViewController是iOS应用程序中重要的部分,是应用程序数据和视图之间的重要桥梁,ViewController管理应用中的众多视图.iOS的SDK中提供很多原生ViewController ...

  7. Vue2学习笔记:实例生命周期

    实例生命周期 每个 Vue 实例在被创建之前都要经过一系列的初始化过程.例如,实例需要配置数据观测(data observer).编译模版.挂载实例到 DOM ,然后在数据变化时更新 DOM .在这个 ...

  8. VUE 学习笔记 二 生命周期

    1.除了数据属性,Vue 实例还暴露了一些有用的实例属性与方法.它们都有前缀 $,以便与用户定义的属性区分开来 var data = { a: 1 } var vm = new Vue({ el: ' ...

  9. Android学习笔记_18_Activity生命周期 及 跳转方式

    一.Activity有三个状态: 1.当它在屏幕前台时(位于当前任务堆栈的顶部),它是激活或运行状态.它就是响应用户操作的Activity. 2. 当它上面有另外一个Activity,使它失去了焦点但 ...

随机推荐

  1. 在Main中定义student的结构体,进行年龄从大到小依次排序录入学生信息。(结构体的用法以及冒泡排序)

    using System; using System.Collections; using System.Collections.Generic; using System.Linq; using S ...

  2. udf提权方法和出现问题汇总

    一.适用条件 1.目标系统是Windows(Win2000,XP,Win2003): 2.你已经拥有MYSQL的某个用户账号,此账号必须有对mysql的insert和delete权限以创建和抛弃函数( ...

  3. [Angularjs]angular ng-repeat与js特效加载先后导致的问题

    写在前面 最近在项目中遇到这样的一个前端的bug,在ng-repeat中绑定的图片,有一个晃动的特效,在手机端浏览的时候,图片有时候会正常展示,有时就展示不出来.当时猜测是因为angularjs与特效 ...

  4. ctags 的最简单使用

    vim插件ctags的安装和使用 2013-11-19 20:47 17109人阅读 评论(0) 收藏 举报  分类: 开发工具(3)  linux编程(9)  c/c++编程(11)  版权声明:本 ...

  5. [整理]iOS开发学习

    最近想趁着休假,花点时间了解下最新的iOS8下的新特性以及Swift语言(想大致了解下和Objective-C有了哪些改进和不同) 可以通过Chris Lattner:Swift 编程语言首席架构师初 ...

  6. 《深入PHP与jQuery开发》读书笔记——Chapter1

    由于去实习过后,发现真正的后台也要懂前端啊,感觉javascript不懂,但是之前用过jQuery感觉不错,很方便,省去了一些内部函数的实现. 看了这一本<深入PHP与jQuery开发>, ...

  7. artEditor增加表单提交功能

    摘要: artEditor.js是一款移动端的富文本编辑器,支持图片上传,后面会增加表情.小视频等功能.最近有朋友反馈artEditor是否支持表单提交,当然是支持的,在未开发该功能之前,你可以像下面 ...

  8. mysql 多表联合更新

    UPDATE mall_order moLEFT JOIN mall_order_goods mog ON mo.id = mog.order_idSET mo.ratio=0.08WHERE mog ...

  9. 第三方平台正式支持接入微信公众平台JS-SDK

    之前微信公众平台面向开发者开放微信内网页开发工具包,现在第三方平台也能正式支持接入微信公众平台JS-SDK了,第三方平台可以在获得公众号的授权后,通过JS-SDK帮助公众号开发和实现网页业务. 公众号 ...

  10. 如何看K线图基础知识

    在日K线图中一般白线.黄线.紫线.绿线依次分别表示:5.10.20.60日移动平均线,但这并不是固定的,会根据设置的不同而不同,比如你也可以在系统里把它们设为5.15.30.60均线. 你看K线图的上 ...