ASP.NET请求过程-Module
管道模型
上图中为Http请求在Asp.net程序中处理的过程。管道处理模型来自上面的HttpApplication,管道处理模型其实就是多个Module(其实这些module都是在往httpapplicatioin里面的事件注册方法 是一个观察者模式) 加一个Handler(Asp.net中所有的请求都会有一个且只有一个handler处理),处理过程就是 请求经过一系列module到达handler 然后handler处理我们的业务代码 然后在经过另外一系列module返回 所有处理的信息都放在httpcontext。一般程序员写的程序全部在Handler中(webform),webform中页面都继承了page类,这个类继承了IHttpHandler。但是在asp.net mvc中控制器并没有继承IHttpHandler,但是在控制器中有一个属性。
HttpApplication、事件
HttpApplication定义:定义 ASP.NET 应用程序中的所有应用程序对象共有的方法、属性和事件。此类是用户在 Global.asax 文件中所定义的应用程序的基类。
每一个用户请求过来,HttpApplicationFactory都会分配一个HTTPApplication对象(这里有一个对象池的概念),
HttpApplication特点:
HttpApplication对象是经由HttpApplicationFactory.GetApplicationInstance(并最终调用HttpRuntime.CreateNonPublicInstance)创建的HttpApplicationFactory它的主要任务是使用 URL 信息来查找 URL 虚拟目录和汇集的 HttpApplication 对象之间的匹配关系。
1、工厂类维护, HttpApplication 对象池并使用它们来处理应用程序的请求。池的寿命与应用程序的寿命相同。
2、应用程序的第一个请求到达时,工厂类(工厂类和HTTPRuntime都是单例的)提取有关应用程序类型的信息(global.asax 类)、设置用于监视更改的文件、创建应用程序状态并触发 Application_OnStart 事件(因为HttpApplicationFactory中有一个静态属性(bool值)记录是否第一次调用,每次创建GetApplicationInstance都会检查这个属性 确定是否执行Application_OnStart 具体可以查看源码)。工厂类从池中获取一个 HttpApplication 实例,并将要处理的请求放入实例中。如果没有可用的对象,则创建一个新的 HttpApplication 对象。要创建 HttpApplication 对象,需要先完成 global.asax 应用程序文件的编译。
internal static IHttpHandler GetApplicationInstance(HttpContext context) { if (HttpApplicationFactory._customApplication != null) { return HttpApplicationFactory._customApplication; } if (context.Request.IsDebuggingRequest) { return new HttpDebugHandler(); } HttpApplicationFactory._theApplicationFactory.EnsureInited(); HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context); return HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context); }
创建过程
HttpApplicationFactory._theApplicationFactory.EnsureInited() 该方法检查HttpApplicationFactory是否被初始化,如果没有,就通过HttpApplicationFactory.Init()进行初始化。在Init()中,先获取global.asax文件的完整路径,然后调用CompileApplication()对global.asax进行编译。
HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context) 创建特定的HttpApplication实例,触发ApplicationOnStart事件,执行ASP.global_asax中的Application_Start(object sender, EventArgs e)方法。这里创建的HttpApplication实例在处理完事件后,就被回收。
HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context) 该方法创建HttpApplication实例并进行初始化,调用System.Web.HttpApplication.InitInternal()方法。创建HttpApplication实例是根据实际的_theApplicationType进行创建。如果Web目录中没有global.asa文件,也就是说没有动态编译生成ASP.global_asax类型,那就直接实例化 HttpApplication。如果创建了ASP.global_asax类型,那就对ASP.global_asa进行实例化。
创建HttpApplication实例之后就是调用实例的InitInternal方法。
InitInternal方法的主要功能如下:
(1. InitModules():根据Web.Config的设置,创建相应的HttpModules。(.net framework 自带的有配置文件,还有项目中的配置文件)
(2. HookupEventHandlersForApplicationAndModules(MethodInfo[] handlers):根据发生的事件,调用HttpApplication实例中相应的事件处理函数。 参数是一个方法数组,是从HttpApplication中反射出来的。
private void HookupEventHandlersForApplicationAndModules(MethodInfo[] handlers) { this._currentModuleCollectionKey = "global.asax"; if (this._pipelineEventMasks == null) { Dictionary<string, RequestNotification> dictionary = new Dictionary<string, RequestNotification>(); this.BuildEventMaskDictionary(dictionary); if (this._pipelineEventMasks == null) { this._pipelineEventMasks = dictionary; } } for (int i = 0; i < handlers.Length; i++) { MethodInfo methodInfo = handlers[i]; string name = methodInfo.Name; int num = name.IndexOf('_'); string text = name.Substring(0, num); object obj = null; if (StringUtil.EqualsIgnoreCase(text, "Application")) { obj = this; } else { if (this._moduleCollection != null) { obj = this._moduleCollection[text]; } } if (obj != null) { Type type = obj.GetType(); EventDescriptorCollection events = TypeDescriptor.GetEvents(type); string text2 = name.Substring(num + 1); EventDescriptor eventDescriptor = events.Find(text2, true); if (eventDescriptor == null && StringUtil.EqualsIgnoreCase(text2.Substring(0, 2), "on")) { text2 = text2.Substring(2); eventDescriptor = events.Find(text2, true); } MethodInfo methodInfo2 = null; if (eventDescriptor != null) { EventInfo @event = type.GetEvent(eventDescriptor.Name); if (@event != null) { methodInfo2 = @event.GetAddMethod(); } } if (!(methodInfo2 == null)) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length == 1) { Delegate @delegate = null; if (methodInfo.GetParameters().Length == 0) { if (parameters[0].ParameterType != typeof(EventHandler)) { goto IL_1F9; } @delegate = new ArglessEventHandlerProxy(this, methodInfo).Handler; } else { try { @delegate = Delegate.CreateDelegate(parameters[0].ParameterType, this, name); } catch { goto IL_1F9; } } try { methodInfo2.Invoke(obj, new object[] { @delegate }); } catch { if (HttpRuntime.UseIntegratedPipeline) { throw; } } if (text2 != null && this._pipelineEventMasks.ContainsKey(text2)) { if (!StringUtil.StringStartsWith(text2, "Post")) { this._appRequestNotifications |= this._pipelineEventMasks[text2]; } else { this._appPostNotifications |= this._pipelineEventMasks[text2]; } } } } } IL_1F9:; } }
就是在global.asa中写的方法,会注册到 HttpApplicatioin中的事件去。 规则以Applicatioin开头下划线分割后面是事件名字(注意Application_Start 找不到Start事件 所以没有注册)。 不是以Application开头的,下划线前面是Module名字,后面是Module中的事件名字。
下面这段代码:根据上面的介绍 把error这个事件注册放在application_start中,这样的话,只有第一次请求的application会有error动作。 这个代码是在一个项目中看到的,问了老员工,貌似他们也是糊里糊涂的。
protected void Application_Start() { Error += MvcApplication_Error; } void MvcApplication_Error(object sender, EventArgs e) { }
(3. 创建很多实现IExecutionStep接口的类的实例并添加到当前HttpApplication实例的_execSteps中,等待回调时执行。从这里我们可以看到HttpApplication是以异步的方式处理请求, 对请求的很多处理工作都放入了_execStep等待回调时执行。
3、HttpApplication 开始处理请求,并且只能在完成这个请求后才能处理新的请求。如果收到来自同一资源的新请求,则由池中的其他对象来处理。
4、应用程序对象允许所有注册的 HTTP 模块对请求进行预处理,并找出最适合处理请求的处理程序类型。这通过查找请求的 URL 的扩展和配置文件中的信息来完成。
HttpApplication事件:
.NETFramework4.5提供了25个标准事件。
// // 摘要: // 当 ASP.NET 获取与当前请求关联的当前状态(如会话状态)时发生。 public event EventHandler AcquireRequestState; // // 摘要: // 当安全模块已建立用户标识时发生。 public event EventHandler AuthenticateRequest; // // 摘要: // 当安全模块已验证用户授权时发生。 public event EventHandler AuthorizeRequest; // // 摘要: // 在 ASP.NET 响应请求时作为 HTTP 执行管线链中的第一个事件发生。 public event EventHandler BeginRequest; // // 摘要: // 在释放应用程序时发生。 public event EventHandler Disposed; // // 摘要: // 在 ASP.NET 响应请求时作为 HTTP 执行管线链中的最后一个事件发生。 public event EventHandler EndRequest; // // 摘要: // 当引发未经处理的异常时发生。 public event EventHandler Error; // // 摘要: // 恰好在 ASP.NET 为当前请求执行任何记录之前发生。 public event EventHandler LogRequest; // // 摘要: // 在选择了用来响应请求的处理程序时发生。 public event EventHandler MapRequestHandler; // // 摘要: // 在已获得与当前请求关联的请求状态(例如会话状态)时发生。 public event EventHandler PostAcquireRequestState; // // 摘要: // 当安全模块已建立用户标识时发生。 public event EventHandler PostAuthenticateRequest; // // 摘要: // 在当前请求的用户已获授权时发生。 public event EventHandler PostAuthorizeRequest; // // 摘要: // 在 ASP.NET 处理完 System.Web.HttpApplication.LogRequest 事件的所有事件处理程序后发生。 public event EventHandler PostLogRequest; // // 摘要: // 在 ASP.NET 已将当前请求映射到相应的事件处理程序时发生。 public event EventHandler PostMapRequestHandler; // // 摘要: // 在 ASP.NET 已完成所有请求事件处理程序的执行并且请求状态数据已存储时发生。 public event EventHandler PostReleaseRequestState; // // 摘要: // 在 ASP.NET 事件处理程序(例如,某页或某个 XML Web service)执行完毕时发生。 public event EventHandler PostRequestHandlerExecute; // // 摘要: // 在 ASP.NET 跳过当前事件处理程序的执行并允许缓存模块满足来自缓存的请求时发生。 public event EventHandler PostResolveRequestCache; // // 摘要: // 在 ASP.NET 完成缓存模块的更新并存储了用于从缓存中为后续请求提供服务的响应后,发生此事件。 public event EventHandler PostUpdateRequestCache; // // 摘要: // 恰好在 ASP.NET 开始执行事件处理程序(例如,某页或某个 XML Web services)前发生。 public event EventHandler PreRequestHandlerExecute; // // 摘要: // 恰好在 ASP.NET 向客户端发送内容之前发生。 public event EventHandler PreSendRequestContent; // // 摘要: // 恰好在 ASP.NET 向客户端发送 HTTP 标头之前发生。 public event EventHandler PreSendRequestHeaders; // // 摘要: // 在 ASP.NET 执行完所有请求事件处理程序后发生。该事件将使状态模块保存当前状态数据。 public event EventHandler ReleaseRequestState; // // 摘要: // 发生,在与该请求已释放的托管对象。 public event EventHandler RequestCompleted; // // 摘要: // 在 ASP.NET 完成授权事件以使缓存模块从缓存中为请求提供服务后发生,从而绕过事件处理程序(例如某个页或 XML Web services)的执行。 public event EventHandler ResolveRequestCache; // // 摘要: // 当 ASP.NET 执行完事件处理程序以使缓存模块存储将用于从缓存为后续请求提供服务的响应时发生。 public event EventHandler UpdateRequestCache;
.NETFramework 4.0,提供了19个标准事件。
1.BeginRequest:asp.net开始处理请求的第一个事件,表示处理的开始。
2.AuthenticateRequest:验证请求,一般用来取得请求的用户信息。
3.PostAuthenticateRequest:已经获取请求的用户信息。
4.AuthorizeRequest:授权,一般用来检查用户的请求是否获得权限。
5.PostAuthorizeRequest:用户请求已经获得授权。
6.ResolveRequestCache:获取以前处理缓存的处理结果,如果以前缓存过,那么,不用再进行请求的处理工作,直接返回缓存的结果。
7.PostResolveRequestCache:已经完成缓存的处理工作。
8.PostMapRequestHandler:已经根据用户的请求,创建了请求的处理器对象。
9.AcquireRequestState:取得请求的状态,一般用于session
10.PostAcquireRequestState:已经获得了session
11.PreRequestHandlerExecute:准备执行处理程序。
12.PostRequestHandlerExecute:已经执行了处理程序
13.ReleaseRequestState:释放请求的状态。
14.PostReleaseRequestState:已经释放了请求的状态。
15.UpdateRequestCache:更新缓存。
16.PostUpdateRequestCache:已经更新了缓存。
17.LogRequest:请求的日志操作
18.PostLogRequest:已经完成请求的日志操作。
19.EndRequest:本次请求处理完成。
其实这些事件我也很好用到。。。顶多四五个常用的。
HttpContext
定义:封装有关个别 HTTP 请求的所有 HTTP 特定的信息。
其实就是Http请求以及返回的所有信息都在里面。
HttpContext来自System.Runtime.Remoting.Messaging.CallContext.HostContext。这个HostContext定义是 获取或设置与当前线程相关联的主机上下文 (所以HttpContext只能在当前请求的线程中使用。)
//为当前 HTTP 请求获取或设置 System.Web.HttpContext 对象。 var con = System.Web.HttpContext.Current; ///HostContext 获取或设置与当前线程相关联的主机上下文。HttpContext来自HostContext HttpContext context2 = System.Runtime.Remoting.Messaging.CallContext.HostContext as HttpContext; //MVC中 HttpContextBase var conbase = base.HttpContext;
CallContext:是线程内部唯一的独用的数据槽(一块内存空间)
//获取或设置与当前线程相关联的主机上下文
public static object HostContext { get; set; }
//清空具有指定名称的数据槽。 public static void FreeNamedDataSlot(string name);
//从 System.Runtime.Remoting.Messaging.CallContext 中检索具有指定名称的对象。 public static object GetData(string name);
// public static Header[] GetHeaders();
//从逻辑调用上下文中检索具有指定名称的对象。 public static object LogicalGetData(string name); public static void LogicalSetData(string name, object data); public static void SetData(string name, object data); public static void SetHeaders(Header[] headers);
属性名称 | 说明 |
---|---|
Application | 为当前 HTTP 请求获取 HttpApplicationState 对象。 |
Cache | 获取当前应用程序域的 Cache 对象。 |
Current | 为当前 HTTP 请求获取或设置 HttpContext 对象。 |
CurrentHandler | 获取表示当前正在执行的处理程序的 IHttpHandler 对象。 |
Handler | 获取或设置负责处理 HTTP 请求的 IHttpHandler 对象。 |
Items | 获取可用于在 HTTP 请求过程中在 IHttpModule 接口和 IHttpHandler 接口之间组织和共享数据的键/值集合。 也就是集合在 HTTP 请求过程中可以用于在模块与处理程序之间组织和共享数据。 |
PreviousHandler | 获取父处理程序的 IHttpHandler 对象。 |
Profile | 获取当前用户配置文件的 ProfileBase 对象。 |
Request | 为当前 HTTP 请求获取 HttpRequest 对象。 |
Response | 为当前 HTTP 响应获取 HttpResponse 对象。 |
Server | 获取提供用于处理 Web 请求的方法的 HttpServerUtility 对象。 |
Session | 为当前 HTTP 请求获取 HttpSessionState 对象。 |
SkipAuthorization | 获取或设置一个值,该值指定 UrlAuthorizationModule 对象是否应跳过对当前请求的授权检查。 |
Timestamp | 获取当前 HTTP 请求处理请求的时间点 |
方法名称 | 说明 |
---|---|
AddError | 将异常添加到当前 HTTP 请求的异常集合中。 |
ClearError | 清除当前 HTTP 请求的所有错误。 |
GetGlobalResourceObject | 已重载。 获取应用程序级别的资源。 |
GetLocalResourceObject | 已重载。 获取页级别的资源。 |
GetSection | 获取当前应用程序的默认配置的指定配置节。 |
RemapHandler | 用于为请求指定处理程序。 |
RewritePath | 重写路径,以便后续的Asp.net以为这是才是真正的地址。RewritePath用在无 Cookie 会话状态中。 |
Global.asax
Gloable上面已经讲过了,这里总结一下。主要就是在请求的时候配置HttpApplication中的事件(每次请求都会执行的,)。以及第一次请求初始化事件(Application_Start() 第一次请求执行) 以及程序停止结束的事件(Application_End() 程序停掉执行)。
Module
上面讲了HTTPApplication中的事件,Module的作用就是往这些事件中注册动作。(观察者模式+配置文件)
public class MyModule : IHttpModule { public void Dispose() { throw new NotImplementedException(); } public void Init(HttpApplication application) { application.BeginRequest += Application_BeginRequest; } protected void Application_BeginRequest(object sender, EventArgs e) { HttpContext.Current.Response.Write("MyModule:BeginRequest" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")); } }
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } /// <summary> /// 源码中是先注册Module中的方法, 然后才注册Global中的方法 /// </summary> /// <param name="sender">就是当前HttpApplication 可以类型强转</param> /// <param name="e"></param> protected void Application_BeginRequest(object sender, EventArgs e) { HttpContext.Current.Response.Write("Global:BeginRequest" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")); } }
<system.webServer> <modules> <add name="mymo" type="MyHttpModuleDemo.App_Start.MyModule"/> <remove name="FormsAuthentication" /> <remove name="ApplicationInsightsWebTracking" /> <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler" /> </modules> <validation validateIntegratedModeConfiguration="false" /> </system.webServer>
要继承IHttpModule接口。实现方法public void Init(HttpApplication application) 在这个方法里面注册动作,做一些处理。
源码中是先注册Module中的方法, 然后才注册Global中的方法。所以Module中的方法先执行。
Module中方法注册:源码中是通过查找配置文件在反射处理。
Global中方法注册:源码中通过把方法名字拆分判断,Application开头的直接通过后面名字找事件注册。 不是Application开头的找Module ,后面的是Module中的事件,然后把这个方法注册到这个事件里面(典型的是Session_Start这个方法)。
框架中默认的配置文件中,已经配置了一些Module。电脑路径(C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config)
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule" /> <add name="Session" type="System.Web.SessionState.SessionStateModule" /> <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" /> <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" /> <add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" /> <add name="RoleManager" type="System.Web.Security.RoleManagerModule" /> <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" /> <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" /> <add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" /> <add name="Profile" type="System.Web.Profile.ProfileModule" /> <add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" /> <add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
里面有一个Session Module。 ( Global.asax中的 Session_Start() 就会注册到这个Module中)
获取当前请求处理的Module HttpContext.ApplicationInstance.Modules