http://referencesource.microsoft.com/
理解ASP.NET的前提是对ASP.NET管道式设计的深刻认识。而ASP.NET Web应用大都是寄宿于IIS上的。
IIS(Internet Information Services)
HTTP请求沿着IIS和ASP.NET管道流动,在这个过程中完成处理,最后得到相应的HTTP响应,发送回客户端。而不同的IIS版本,处理方式有着不小的差异。
IIS 5.x (windows xp)
IIS 5.x 运行在进程InetInfo.exe中,该进程中寄宿着名为W3SVC(World Wide Web Publishing Service)的windows服务。这个服务主要负责3个任务,如下图:
当监测到某个HTTP请求时,IIS首先依据扩展名确定是静态资源,亦或动态资源。前者直接返回文件内容,后者通过IIS的映射脚本找到相应的ISAPI动态链接库。然后这个动态链接库会被加载入IIS进程,随后ISAPI会创建工作进程。工作进程运行在托管环境,通过命名管道(Named Pipes)与IIS进程通信。每个Web应用都运行在独立的应用程序域(Application Domain)中,映射一个IIS虚拟目录。而所有的应用程序域都在同一个工作进程中。
ISAPI支持
ISAPI扩展
ISAPI(Internet Server Appliaction Programming Interface)是一套本地的WIN32 API,是IIS和其他动态Web应用或平台之间的纽带。开发者可以使用这些接口深入到IIS,让IIS支持各种其他处理程序。ISAPI是自定义Web请求处理中第一个IIS入口点。
根据扩展名的不同,依据映射脚本选择不同的ISAPI动态链接库处理:
asp.net -》 aspnet_isapi.dll
php -》 php4isapi.dll
可以在IIS中配置,比如把*.html文件也当动态资源处理,这样就可以在经典模式中对静态页做一些验证、拦截、改写等操作,如伪静态或静态页生成功能的实现。
ISAPI筛选
筛选器则如同在应用中的AOP模式一样,为请求处理过程横向增添一些操作,如日志记录,身份验证等。
IIS 6(windows server 2003)
IIS 5.x中主要有两大不足
1. ISAPI被加载到InetInfo.exe进程中,它和工作进程之间是跨线程通信,尽管使用命名管道,但仍然会带来性能的瓶颈。
2. 所有Web应用运行在同一个工作进程,虽然有基于应用程序域的隔离,但不能从根本上解决一个应用对另一个应用的影响。
II6 的主要变动
1. 引入程序池的机制,可以创建一个或多个应用程序池,每个应用程序池对于一个独立的工作进程。一个应用程序池可以承载一个或多个Web应用。
2. ISAPI被直接加载到工作进程中
3. W3SVC服务从InetInfo.exe进程中脱离出来,运行在另一个进程SvcHost.exe中。而元数据库Metabase依然存在于InetInfo.exe中。
4. 引入名为HTTP.SYS的HTTP监听器,以驱动程序的方式运行在windows的内核模式下,是windows TCP/IP网络子系统的一部分。它已经不属于IIS了,它的配置信息并没有保存在IIS元数据库(Metabase)中,而是定义在注册表中。
经典模式的请求处理
1. 请求的接收
http.sys组件监听到HTTP请求后,联系W3SVC,后者会根据IIS中的 Metabase 查看基于该 Request 的 Application 属于哪个Application Pool。如果该Application Pool不存在,则创建之、否则直接将 Request 发到对应Application Pool 的 Queue中。
2. 请求的传递
每个 Application Pool 都对应着一个Worker Process(w3wp.exe)。W3SVC会依据在IIS Metabase 中维护着的 Application Pool 和w3wp的映射关系,将存在于某个Application Pool Queue中的Request传递给对应的worker Process。
3. 请求的处理
worker process不存在时,会自动创建,而在其初始化的时候,会加载ASP.NET ISAPI,从而在w3wp.exe内部,ASP.NET以IIS API extension的方式外加到IIS。
ASP.NET ISAPI进而加载CLR,为ASP.NET Application创建一个托管的运行环境。
在CLR初始化的时候会加载两个重要的对象:AppManagerAppDomainFactory和ISAPIRuntime。通过AppManagerAppDomainFactory的Create方法为Application创建一个Application Domain;通过ISAPIRuntime的ProcessRequest封装Request,进而将请求转入到ASP.NET Http Runtime Pipeline。
IIS 7(windows server 2008)
IIS 7 的主要变动
1. 引入进程激活服务WAS(Windows Process Activation Service)分流了W3SVC的部分功能。WAS为IIS引入了对非HTTP协议的支持。
2. IIS的配置信息不再基于Metabase,而是大都存放于XML文件中,基本配置则存放在applicationHost.config。
3. 引入集成管道
- HTTP.sys监听拦截客户端请求开始处理。
- HTTP.sys通过配置信息联系WAS获取相关信息。
- WAS 向配置存储中心请求配置信息。applicationHost.config。
- WWW 服务接受到配置信息(应用程序池配置信息,站点配置信息等),使用配置信息去配置 HTTP.sys 处理策略。
- WAS为这个请求对应的应用程序池(Application Pool)开启W3WP Worker Process。
- W3WP Worker Process处理以后,将Response返回给HTTP.sys。
- 客户端接受到Response内容。
IIS7目前有2个模式: 经典模式和集成模式。
经典模式
经典模式的W3WP.exe工作方式就是IIS 5.x 、IIS 6的模式。即: IIS ISAPI extension,也就是使用 aspnet_isapi.dll。
经典模式中IIS和ASP.NET是两个独立的管道,在各自的管辖范围内,各自具有自己的一套机制对HTTP请求进行处理。两个管道通过ISAPI实现连通,IIS是第一道屏障,当对HTTP请求进行必要的前期处理(身份验证等)后,IIS通过ISAPI将请求分发给ASP.NET管道。当ASP.NET在自身管道范围内完成对HTTP的处理后,处理结果会在返回IIS,IIS对其多后期的处理(日志记录、压缩等)后生成HTTP回复对请求予以响应。
局限:
1. 相同操作的重复执行。如两个管道都要进行身份验证。
2. 动态文件和静态文件的处理不一致,静态文件是不进ASP.NET管道的。那么ASP.NET管道中的一些功能就不能作用于这些基于静态文件的请求。
3. IIS难以扩展,因为ISAPI是基于win32的非托管API。
集成模式
IIS7集成模式则让IIS集成了.NET功能(不再依靠之前IIS版本的aspnet_isapi.dll)。
好处:
1. 允许通过本地代码和托管代码两种方式定义IIS Module,这些IIS Module 注册到IIS中将形成一个通用的请求处理管道,能够处理所有的请求。
2. 将Asp.Net提供的一些强大功能应用到原来难以企及的地方,比如URL重写功能置于身份验证之前。
3. 采用相同的方式实现、配置。检测和支持一些服务器特性,比如Module、Handler映射、定制错误配置等。
4. 在集成模式下所有的请求都要经过.Net来处理(包括Html,PHP等),也因为.Net的诸多功能成为IIS的一部分,性能上也得到了提升。
IIS 8(windows server 2012 or windows 8)
变化:
1. Application的初始化被包括在IIS 8.0中, 而在IIS 7.5中 Application 初始化(RC)被作为一个外带模块。
2. IIS 8.0的管理工具已经为ASP.net 4.5功能更新。
3. IIS 8.0 集成了SSL认证。
4. IIS 8.0 CPU节流已经得到更新,且包括额外的节流选项。
5. IIS 8.0 集成动态IP地址的限制功能。
6. IIS 8.0 集成了FTP尝试登陆限制功能。
7. IIS 8.0 在NUMA 上的多核扩展。
----
HttpWorkerRequest
由非托管代码生成的HttpWorkerRequest对象,包含当前请求的所有信息。
在经典模式下请求被封装为System.Web.Hosting.ISAPIWorkerRequest,而在集成模式下请求则会被封装为System.Web.Hosting.IIS7WorkerRequest,它们都是HttpWorkerRequest的子类。
HttpWorkerRequest对象会被传递给HttpRuntime,在我们的页面中可以直接通过它取得原始的请求信息。
经典模式下HttpWorkerRequest的生成
进入ASP.NET管道(经典模式)
System.Web.Hosting.IISAPIRuntime
ASP.NET ISAPI运行在一个非托管环境之中。经过一系列COM级别的class调用,最终的调用降临到一个托管的、继承自System.Web.Hosting.ISAPIRuntime类的对象上。ISAPIRuntime 是一个特殊的class,他实现了接口System.Web.Hosting.IISAPIRuntime。这是一个基于COM的Interface,也就是说Caller可以通过COM的方式调用实现该Interface的Class的对象。在这里,这个最初的Caller就是ASP.NET ISAPI。ASP.NET ISAPI通过调用System.Web.Hosting.ISAPIRuntime Instance的ProcessRequest方法,进而从非托管的环境进入了托管的环境。
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("08a2c56f-7c16-41c1-a8be-432917a1a2d1")] public interface IISAPIRuntime { void StartProcessing(); void StopProcessing(); [return: MarshalAs(UnmanagedType.I4)] int ProcessRequest([In] IntPtr ecb, [In, MarshalAs(UnmanagedType.I4)] int useProcessModel); void DoGCCollect(); }
ISAPI ECB (Execution Control Block)
通过System.Web.Hosting.IISAPIRuntime 接口中的ProcessRequest方法的签名,可以看出该方法包含两个参数,其中一个是名为ecb的Unmanaged Pointer,另一个是useProcessModel。
ECB全称是Execution Control Block,在整个Http Request Processing过程中起着非常重要的作用。
ISAPI顾名思义,就是实现了一些基于Internet Server的API。aspnet_isapi.dll实现了这些API,对于IIS来说,它可以调用这些API进入托管的环境实现对ISAPIRuntime的调用,对于ISAPIRuntime来说,它需要调用ASP.NET ISAPI实现一些必要的功能,比如获得Server Variable的数据,获得通过Post Mehod传回Server的数据;以及最终将Response的内容返回给ASP.NET ISAPI,并通过ASP.NET ISAPI返回到Client。一般地ISAPIRuntime不能直接调用ASP.NET ISAPI,而是通过一个对象指针实现对其的调用,这个对象就是ECB,ECB实现了对ISAPI的访问。
特别需要强调的是,ISAPI对ISAPIRutime的调用是异步的,也就是说ISAPI调用ISAPIRutime之后立即返回。这主要是出于Performance和Responsibility考虑的,因为ASP.NET Application天生就是一个多线程的应用,为了具有更好的响应能力,异步操作是最有效的解决方式。但是这里就会有一个问题,我们对ASP.NET 资源的调用本质上是一个Request/Response的Message Exchange Pattern,异步调用往往意味着ISAPI将Request传递给ISAPIRuntime,将不能得到ISAPIRuntime最终生成的Response,这显然是不能接受的。而ECB解决了这个问题,ISAPI在调用ISAPIRutime的ProcessRequest方法时会将自己对应的ECB的指针传给它,ISAPIRutime不但可以将最终生成的Response返回给ISAPI,还能通过ECB调用ISAPI获得一些所需的数据。
ISAPIWorkerRequest(HttpWorkerRequest的子类)
ISAPIRutime的ProcessRequest的实现:
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)] // DevDiv #180492 public int ProcessRequest(IntPtr ecb, int iWRType) { IntPtr pHttpCompletion = IntPtr.Zero; if (iWRType == WORKER_REQUEST_TYPE_IN_PROC_VERSION_2) { pHttpCompletion = ecb; ecb = UnsafeNativeMethods.GetEcb(pHttpCompletion); } ISAPIWorkerRequest wr = null; try { bool useOOP = (iWRType == WORKER_REQUEST_TYPE_OOP); wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP); wr.Initialize(); // check if app path matches (need to restart app domain?) String wrPath = wr.GetAppPathTranslated(); String adPath = HttpRuntime.AppDomainAppPathInternal; if (adPath == null || StringUtil.EqualsIgnoreCase(wrPath, adPath)) { HttpRuntime.ProcessRequestNoDemand(wr); return 0; } else { // need to restart app domain HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged, SR.GetString(SR.Hosting_Phys_Path_Changed, adPath, wrPath)); return 1; } } catch(Exception e) { try { WebBaseEvent.RaiseRuntimeError(e, this); } catch {} // Have we called HSE_REQ_DONE_WITH_SESSION? If so, don't re-throw. if (wr != null && wr.Ecb == IntPtr.Zero) { if (pHttpCompletion != IntPtr.Zero) { UnsafeNativeMethods.SetDoneWithSessionCalled(pHttpCompletion); } // if this is a thread abort exception, cancel the abort if (e is ThreadAbortException) { Thread.ResetAbort(); } // IMPORTANT: if this thread is being aborted because of an AppDomain.Unload, // the CLR will still throw an AppDomainUnloadedException. The native caller // must special case COR_E_APPDOMAINUNLOADED(0x80131014) and not // call HSE_REQ_DONE_WITH_SESSION more than once. return 0; } // re-throw if we have not called HSE_REQ_DONE_WITH_SESSION throw; } } int IISAPIRuntime2.ProcessRequest(IntPtr ecb, int iWRType) { return ProcessRequest(ecb, iWRType); }
ISAPI在调用ISAPIRuntime的时候将对应的ISAPI ECB Pointer作为参数传递给了ProcessRequest方法,这个ECB pointer可以看成是托管环境和非托管环境进行数据交换的唯一通道,Server Variable和Request Parameter通过它传入ASP.NET作为进一步处理的依据,ASP.NET最后生成的Response通过它传递给ISAPI,并进一步传递给IIS最终返回到Client端。
ISAPIRutime的ProcessRequest方法完成下面两个任务:
1. 通过传入的ECB和iWRType创建一个叫做ISAPIWorkerRequest的对象
2. 调用HttpRuntime.ProcessRequestNoDemand(wr),真正进入了ASP.NET Runtime Pipeline。
ISAPIWorkerRequest是一个Abstract class,它已通过ECB创建基于当前Request的Context的信息,针对不同的IIS版本,具有不同的ISAPIWorkerRequest 子类,ProcessRequest通过ISAPI传入的iWRType来创建不同HttpWorkerRequest(internal abstract class ISAPIWorkerRequest : HttpWorkerRequest),从而屏蔽了不同IIS的差异。
internal static ISAPIWorkerRequest CreateWorkerRequest(IntPtr ecb, bool useOOP) { ISAPIWorkerRequest wr = null; if (useOOP) { EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero); if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, false); wr = new ISAPIWorkerRequestOutOfProc(ecb); } else { int version = UnsafeNativeMethods.EcbGetVersion(ecb) >> 16; if (version >= 7) { EtwTrace.TraceEnableCheck(EtwTraceConfigType.IIS7_ISAPI, ecb); } else { EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero); } if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, true); if (version >= 7) { wr = new ISAPIWorkerRequestInProcForIIS7(ecb); } else if (version == 6) { wr = new ISAPIWorkerRequestInProcForIIS6(ecb); } else { wr = new ISAPIWorkerRequestInProc(ecb); } } return wr; }
集成模式下的ASP.NET管道
IPipelineRuntime
[Guid("c96cb854-aec2-4208-9ada-a86a96860cb6")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IPipelineRuntime { IntPtr GetAsyncCompletionDelegate(); IntPtr GetAsyncDisconnectNotificationDelegate(); IntPtr GetDisposeDelegate(); IntPtr GetExecuteDelegate(); IntPtr GetPrincipalDelegate(); IntPtr GetRoleDelegate(); void InitializeApplication(IntPtr appContext); void StartProcessing(); void StopProcessing(); }
HttpWorkerRequest和HttpContext的创建
public IntPtr GetExecuteDelegate() { if (IntPtr.Zero == _executeDelegatePointer) { lock (_delegatelock) { if (IntPtr.Zero == _executeDelegatePointer) { ExecuteFunctionDelegate d = new ExecuteFunctionDelegate(ProcessRequestNotification); if (null != d) { IntPtr p = Marshal.GetFunctionPointerForDelegate(d); if (IntPtr.Zero != p) { Thread.MemoryBarrier(); _executeDelegate = d; _executeDelegatePointer = p; } } } } } return _executeDelegatePointer; } internal static int ProcessRequestNotification( IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, int flags) { try { return ProcessRequestNotificationHelper(rootedObjectsPointer, nativeRequestContext, moduleData, flags); } catch(Exception e) { ApplicationManager.RecordFatalException(e); throw; } } internal static int ProcessRequestNotificationHelper( IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, int flags) { IIS7WorkerRequest wr = null; HttpContext context = null; RequestNotificationStatus status = RequestNotificationStatus.Continue; RootedObjects root; bool workerRequestWasJustCreated = false; if (rootedObjectsPointer == IntPtr.Zero) { InitializeRequestContext(nativeRequestContext, flags, out wr, out context); workerRequestWasJustCreated = true; if (context == null) { return (int)RequestNotificationStatus.FinishRequest; } root = RootedObjects.Create(); root.HttpContext = context; root.WorkerRequest = wr; root.WriteTransferEventIfNecessary(); context.RootedObjects = root; IIS.MgdSetManagedHttpContext(nativeRequestContext, root.Pointer); } else { root = RootedObjects.FromPointer(rootedObjectsPointer); context = root.HttpContext; wr = root.WorkerRequest as IIS7WorkerRequest; } Debug.Assert(root != null, "We should have a RootedObjects instance by this point."); Debug.Assert(wr != null, "We should have an IIS7WorkerRequest instance by this point."); using (root.WithinTraceBlock()) { if (workerRequestWasJustCreated) { AspNetEventSource.Instance.RequestStarted(wr); } int currentModuleIndex; bool isPostNotification; int currentNotification; IIS.MgdGetCurrentNotificationInfo(nativeRequestContext, out currentModuleIndex, out isPostNotification, out currentNotification); // If the HttpContext is null at this point, then we've already transitioned this request to a WebSockets request. // The WebSockets module should already be running, and asynchronous module-level events (like SendResponse) are // ineligible to be hooked by managed code. if (context == null || context.HasWebSocketRequestTransitionStarted) { return (int)RequestNotificationStatus.Continue; } // It is possible for a notification to complete asynchronously while we're in // a call to IndicateCompletion, in which case a new IIS thread might enter before // the call to IndicateCompletion returns. If this happens, block the thread until // IndicateCompletion returns. But never block a SendResponse notification, because // that can cause the request to hang (DevDiv Bugs 187441). if (context.InIndicateCompletion && context.ThreadInsideIndicateCompletion != Thread.CurrentThread && RequestNotification.SendResponse != (RequestNotification)currentNotification) { while (context.InIndicateCompletion) { Thread.Sleep(10); } } // RQ_SEND_RESPONSE fires out of band and completes synchronously only. // The pipeline must be reentrant to support this, so the notification // context for the previous notification must be saved and restored. NotificationContext savedNotificationContext = context.NotificationContext; bool cancellable = context.IsInCancellablePeriod; bool locked = false; try { if (cancellable) { context.EndCancellablePeriod(); } bool isReEntry = (savedNotificationContext != null); if (isReEntry) { context.ApplicationInstance.AcquireNotifcationContextLock(ref locked); } context.NotificationContext = new NotificationContext(flags /*CurrentNotificationFlags*/, isReEntry); Action<RequestNotificationStatus> verifierCheck = null; if (AppVerifier.IsAppVerifierEnabled) { verifierCheck = AppVerifier.GetRequestNotificationStatusCheckDelegate(context, (RequestNotification)currentNotification, isPostNotification); } status = HttpRuntime.ProcessRequestNotification(wr, context); if (verifierCheck != null) { AppVerifier.InvokeVerifierCheck(verifierCheck, status); } } finally { if (status != RequestNotificationStatus.Pending) { // if we completed the notification, pop the notification context stack // if this is an asynchronous unwind, then the completion will clear the context context.NotificationContext = savedNotificationContext; // DevDiv 112755 restore cancellable state if its changed if (cancellable && !context.IsInCancellablePeriod) { context.BeginCancellablePeriod(); } else if (!cancellable && context.IsInCancellablePeriod) { context.EndCancellablePeriod(); } } if (locked) { context.ApplicationInstance.ReleaseNotifcationContextLock(); } } if (status != RequestNotificationStatus.Pending) { // The current notification may have changed due to the HttpApplication progressing the IIS state machine, so retrieve the info again. IIS.MgdGetCurrentNotificationInfo(nativeRequestContext, out currentModuleIndex, out isPostNotification, out currentNotification); // WOS 1785741: (Perf) In profiles, 8% of HelloWorld is transitioning from native to managed. // The fix is to keep managed code on the stack so that the AppDomain context remains on the // thread, and we can re-enter managed code without setting up the AppDomain context. // If this optimization is possible, MgdIndicateCompletion will execute one or more notifications // and return PENDING as the status. ThreadContext threadContext = context.IndicateCompletionContext; // DevDiv 482614: // Don't use local copy to detect if we can call MgdIndicateCompletion because another thread // unwinding from MgdIndicateCompletion may be changing context.IndicateCompletionContext at the same time. if (!context.InIndicateCompletion && context.IndicateCompletionContext != null) { if (status == RequestNotificationStatus.Continue) { try { context.InIndicateCompletion = true; Interlocked.Increment(ref _inIndicateCompletionCount); context.ThreadInsideIndicateCompletion = Thread.CurrentThread; IIS.MgdIndicateCompletion(nativeRequestContext, ref status); } finally { context.ThreadInsideIndicateCompletion = null; Interlocked.Decrement(ref _inIndicateCompletionCount); // Leave will have been called already if the last notification is returning pending // DTS267762: Make sure InIndicateCompletion is released, not based on the thread context state // Otherwise the next request notification may deadlock if (!threadContext.HasBeenDisassociatedFromThread || context.InIndicateCompletion) { lock (threadContext) { if (!threadContext.HasBeenDisassociatedFromThread) { threadContext.DisassociateFromCurrentThread(); } context.IndicateCompletionContext = null; context.InIndicateCompletion = false; } } } } else { if (!threadContext.HasBeenDisassociatedFromThread || context.InIndicateCompletion) { lock (threadContext) { if (!threadContext.HasBeenDisassociatedFromThread) { threadContext.DisassociateFromCurrentThread(); } context.IndicateCompletionContext = null; context.InIndicateCompletion = false; } } } } } if (context.HasWebSocketRequestTransitionStarted && status == RequestNotificationStatus.Pending) { // At this point, the WebSocket module event (PostEndRequest) has executed and set up the appropriate contexts for us. // However, there is a race condition that we need to avoid. It is possible that one thread has kicked off some async // work, e.g. via an IHttpAsyncHandler, and that thread is unwinding and has reached this line of execution. // Meanwhile, the IHttpAsyncHandler completed quickly (but asynchronously) and invoked MgdPostCompletion, which // resulted in a new thread calling ProcessRequestNotification. If this second thread starts the WebSocket transition, // then there's the risk that *both* threads might attempt to call WebSocketPipeline.ProcessRequest, which could AV // the process. // // We protect against this by allowing only the thread which started the transition to complete the transition, so in // the above scenario the original thread (which invoked the IHttpAsyncHandler) no-ops at this point and just returns // Pending to its caller. if (context.DidCurrentThreadStartWebSocketTransition) { // We'll mark the HttpContext as complete, call the continuation to kick off the socket send / receive loop, and return // Pending to IIS so that it doesn't advance the state machine until the WebSocket loop completes. root.ReleaseHttpContext(); root.WebSocketPipeline.ProcessRequest(); } } return (int)status; } } private static void InitializeRequestContext(IntPtr nativeRequestContext, int flags, out IIS7WorkerRequest wr, out HttpContext context) { wr = null; context = null; try { bool etwEnabled = ((flags & HttpContext.FLAG_ETW_PROVIDER_ENABLED) == HttpContext.FLAG_ETW_PROVIDER_ENABLED); // this may throw, e.g. if the request Content-Length header has a value greater than Int32.MaxValue wr = IIS7WorkerRequest.CreateWorkerRequest(nativeRequestContext, etwEnabled); // this may throw, e.g. see WOS 1724573: ASP.Net v2.0: wrong error code returned when ? is used in the URL context = new HttpContext(wr, false); } catch { // treat as "400 Bad Request" since that's the only reason the HttpContext.ctor should throw IIS.MgdSetBadRequestStatus(nativeRequestContext); } }
http://referencesource.microsoft.com/#System.Web/Hosting/IPipelineRuntime.cs
HttpContext
经典模式
HttpWorkerRequest作为参数传入HttpRuntime.ProcessRequestNoDemand的调用。HttpRuntime.ProcessRequestNoDemand最终体现在调用ProcessRequestInternal。下面是真个方法的实现
private void ProcessRequestInternal(HttpWorkerRequest wr) { // Count active requests Interlocked.Increment(ref _activeRequestCount); if (_disposingHttpRuntime) { // Dev11 333176: An appdomain is unloaded before all requests are served, resulting in System.AppDomainUnloadedException during isapi completion callback // // HttpRuntim.Dispose could have already finished on a different thread when we had no active requests // In this case we are about to start or already started unloading the appdomain so we will reject the request the safest way possible try { wr.SendStatus(503, "Server Too Busy"); wr.SendKnownResponseHeader(HttpWorkerRequest.HeaderContentType, "text/html; charset=utf-8"); byte[] body = Encoding.ASCII.GetBytes("<html><body>Server Too Busy</body></html>"); wr.SendResponseFromMemory(body, body.Length); // this will flush synchronously because of HttpRuntime.ShutdownInProgress wr.FlushResponse(true); wr.EndOfRequest(); } finally { Interlocked.Decrement(ref _activeRequestCount); } return; } // Construct the Context on HttpWorkerRequest, hook everything together HttpContext context; try { context = new HttpContext(wr, false /* initResponseWriter */); } catch { try { // If we fail to create the context for any reason, send back a 400 to make sure // the request is correctly closed (relates to VSUQFE3962) wr.SendStatus(400, "Bad Request"); wr.SendKnownResponseHeader(HttpWorkerRequest.HeaderContentType, "text/html; charset=utf-8"); byte[] body = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>"); wr.SendResponseFromMemory(body, body.Length); wr.FlushResponse(true); wr.EndOfRequest(); return; } finally { Interlocked.Decrement(ref _activeRequestCount); } } wr.SetEndOfSendNotification(_asyncEndOfSendCallback, context); HostingEnvironment.IncrementBusyCount(); try { // First request initialization try { EnsureFirstRequestInit(context); } catch { // If we are handling a DEBUG request, ignore the FirstRequestInit exception. // This allows the HttpDebugHandler to execute, and lets the debugger attach to // the process (VSWhidbey 358135) if (!context.Request.IsDebuggingRequest) { throw; } } // Init response writer (after we have config in first request init) // no need for impersonation as it is handled in config system context.Response.InitResponseWriter(); // Get application instance IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context); if (app == null) throw new HttpException(SR.GetString(SR.Unable_create_app_object)); if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, app.GetType().FullName, "Start"); if (app is IHttpAsyncHandler) { // asynchronous handler IHttpAsyncHandler asyncHandler = (IHttpAsyncHandler)app; context.AsyncAppHandler = asyncHandler; asyncHandler.BeginProcessRequest(context, _handlerCompletionCallback, context); } else { // synchronous handler app.ProcessRequest(context); FinishRequest(context.WorkerRequest, context, null); } } catch (Exception e) { context.Response.InitResponseWriter(); FinishRequest(wr, context, e); } }
集成模式
internal static RequestNotificationStatus ProcessRequestNotification(IIS7WorkerRequest wr, HttpContext context) { return _theRuntime.ProcessRequestNotificationPrivate(wr, context); } private RequestNotificationStatus ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) { RequestNotificationStatus status = RequestNotificationStatus.Pending; try { int currentModuleIndex; bool isPostNotification; int currentNotification; // setup the HttpContext for this event/module combo UnsafeIISMethods.MgdGetCurrentNotificationInfo(wr.RequestContext, out currentModuleIndex, out isPostNotification, out currentNotification); context.CurrentModuleIndex = currentModuleIndex; context.IsPostNotification = isPostNotification; context.CurrentNotification = (RequestNotification) currentNotification; Debug.Trace("PipelineRuntime", "HttpRuntime::ProcessRequestNotificationPrivate: notification=" + context.CurrentNotification.ToString() + ", isPost=" + context.IsPostNotification + ", moduleIndex=" + context.CurrentModuleIndex); IHttpHandler handler = null; if (context.NeedToInitializeApp()) { Debug.Trace("FileChangesMonitorIgnoreSubdirChange", "*** FirstNotification " + DateTime.Now.ToString("hh:mm:ss.fff", CultureInfo.InvariantCulture) + ": _appDomainAppId=" + _appDomainAppId); // First request initialization try { EnsureFirstRequestInit(context); } catch { // If we are handling a DEBUG request, ignore the FirstRequestInit exception. // This allows the HttpDebugHandler to execute, and lets the debugger attach to // the process (VSWhidbey 358135) if (!context.Request.IsDebuggingRequest) { throw; } } context.Response.InitResponseWriter(); handler = HttpApplicationFactory.GetApplicationInstance(context); if (handler == null) throw new HttpException(SR.GetString(SR.Unable_create_app_object)); if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, handler.GetType().FullName, "Start"); HttpApplication app = handler as HttpApplication; if (app != null) { // associate the context with an application instance app.AssignContext(context); } } // this may throw, and should be called after app initialization wr.SynchronizeVariables(context); if (context.ApplicationInstance != null) { // process request IAsyncResult ar = context.ApplicationInstance.BeginProcessRequestNotification(context, _requestNotificationCompletionCallback); if (ar.CompletedSynchronously) { status = RequestNotificationStatus.Continue; } } else if (handler != null) { // HttpDebugHandler is processed here handler.ProcessRequest(context); status = RequestNotificationStatus.FinishRequest; } else { status = RequestNotificationStatus.Continue; } } catch (Exception e) { status = RequestNotificationStatus.FinishRequest; context.Response.InitResponseWriter(); // errors are handled in HttpRuntime::FinishRequestNotification context.AddError(e); } if (status != RequestNotificationStatus.Pending) { // we completed synchronously FinishRequestNotification(wr, context, ref status); } #if DBG Debug.Trace("PipelineRuntime", "HttpRuntime::ProcessRequestNotificationPrivate: status=" + status.ToString()); #endif return status; }
首先通过HttpWorkerRequest创建按一个HttpContext对象,随后通过HttpApplicationFactory.GetApplicationInstance创建一个IHttpHandler对象(一般情况下就是一个HttpApplication对象)。
正如他的名字体现的,HttpContext体现当前Request的上下文信息,它的生命周期知道整个Request处理结束或者处理超时。通过HttpContext对象我们可以访问属于当前Request的一系列常用的对象:Server,Session,Cache,Application,Request,Response,Trace,User,Profile等等。此外我们可以认为将一些数据放在Items属性中作为状态管理的一种方式,不过这种状态管理和其他一些常用的方式,比如Session,Cache,Application,Cookie等,具有根本性的不同之处是其生命周期仅仅维持在当前Request的Context中。
HttpApplication
就像其名称体现的一样,HttpApplication基本上可以看成是真个ASP.NET Application的体现。HttpApplication和置于虚拟根目录的Gloabal.asax对应。通过HttpApplicationFactory.GetApplicationInstance创建一个基于Gloabal.asax的HttpApplication对象。在HttpApplicationFactory.GetApplicationInstance方法返回创建的HttpApplication对象之前,会调用一个名为InitInternal的内部方法,该方法会做一些列的初始化的操作,在这些初始化操作中,最典型的一个初始化方法为InitModules(),该方法的主要的目的就是查看Config中注册的所有HttpModule,并根据配置信息加载相应的Assembly,通过Reflection创建对应的HttpModule,并将这些Module加到HttpApplication 的_moduleCollection Filed中。
HttpApplication对象负责处理分发给它的HTTP请求,一个HttpApplication对象在某个时刻只能处理一个请求。Asp.net采用对象池机制来创建和获取HttpApplication对象。它的工作方式是通过在不同阶段出发不同Event来调用我们注册的Event Hander。
Application和AppDomain的关系
一个Application并不是只运行在一个AppDomain之中。因为存在一种特殊的场景:在当前Application正在处理Request的时候,我们把web.config以及其他一些相关文件修改了,而且这种改变是可以马上被ASP.NET检测到的,为了使我们的变动能够及时生效,对于改动后的第一个Request,ASP.NET会为期创建一个新的AppDomain,而对于原来的AppDomain,也许还在处理修改前的Request,所有原来的Appdomain会持续到将原来的Request处理结束之后,所以对于一个Application,可能出现多个AppDomain并存的现象。
HttpApplication处理请求的整个生命周期中会触发的事件
我们可以注册相应的事件,将处理逻辑注入到HttpApplication处理请求的某个阶段。
名称 |
描述 |
BeginRequest |
HTTP管道开始处理请求时,会触发BeginRequest事件。这个事件标志着Asp.net服务器处理工作的开始,也是程序员在Asp.net中针对请求所能够处理的第一个事件。 |
AuthenticateRequest,PostAuthenticateRequest |
ASP.NET先后触发这两个事件,使安全模块对请求进行身份验证(确定请求用户的身份以实现安全机制)。前者验证身份时触发,后者是已经验证身份后触发。 检查后的用户可以通过HttpContext的User属性获取到 if (HttpContext.Current.User.Identity.IsAuthenticated) |
AuthorizeRequest,PostAuthorizeRequest |
ASP.NET先后触发这两个事件,使安全模块对请求进程授权(检查权限)。如果没有通过安全检查,一般情况下,都跳过剩下的事件,直接触发LogRequest事件。 |
ResolveRequestCache,PostResolveRequestCache |
ASP.NET先后触发这两个事件,以使缓存模块利用缓存的直接对请求直接进程响应(缓存模块可以将响应内容进程缓存,对于后续的请求,直接将缓存的内容返回,从而提高响应能力)。 |
MapRequestHandler, PostMapRequestHandler |
对于访问不同的资源类型,ASP.NET具有不同的HttpHandler对其进程处理。对于每个请求,ASP.NET会通过扩展名选择匹配相应的HttpHandler类型,在匹配前后触发这两个事件。 其中MapRequestHandler在Asp.net 4.0后才可用 |
AcquireRequestState,PostAcquireRequestState |
ASP.NET先后触发这两个事件,使状态管理模块获取基于当前请求相应的状态,比如SessionState |
PreRequestHandlerExecute,PostRequestHandlerExecute |
ASP.NET最终通过一请求资源类型相对应的HttpHandler实现对请求的处理,在实行HttpHandler前后,这两个实现被先后触发 |
ReleaseRequestState,PostReleaseRequestState |
ASP.NET先后触发这两个事件,使状态管理模块释放基于当前请求相应的状态 |
UpdateRequestCache,PostUpdateRequestCache |
ASP.NET先后触发这两个事件,以使缓存模块将HttpHandler处理请求得到的相应保存到输出缓存中 |
LogRequest,PostLogRequest |
ASP.NET先后触发这两个事件为当前请求进程日志记录(Asp.net 4.0) |
EndRequest |
整个请求处理完成后,EndRequest事件被触发 |
PreSendRequestHeaders |
当准备通过HttpResponse回应发送HTTP的Header之前触发。 |
PreSendRequestContent |
当准备通过HttpResponse回应发送HTTP的Body内容之前触发。 |
Error |
在出现未处理的错误时触发 |
RequestCompleted |
处理完成后触发,此时Context已不存在 |
HttpModule
HttpModule是ASP.NET管道提供的扩展机制,通过它将所需操作注入到HttpApplication处理请求的某个阶段。
HttpApplication对象初始化过程中,会根据配置文件加载并初始化注册的所有HttpModule对象。
自定义HttpModule
public class Module1:IHttpModule { public void Init(HttpApplication context) { context.BeginRequest += BeginRequest; context.AuthenticateRequest += AuthenticateRequest; context.PostAuthenticateRequest += PostAuthenticateRequest; context.AuthorizeRequest += AuthorizeRequest; context.PostAuthorizeRequest += PostAuthorizeRequest; context.ResolveRequestCache += ResolveRequestCache; context.PostResolveRequestCache += PostResolveRequestCache; context.MapRequestHandler += MapRequestHandler; context.PostMapRequestHandler += PostMapRequestHandler; context.AcquireRequestState += AcquireRequestState; context.PostAcquireRequestState += PostAcquireRequestState; context.PreRequestHandlerExecute += PreRequestHandlerExecute; context.PostRequestHandlerExecute += PostRequestHandlerExecute; context.ReleaseRequestState += ReleaseRequestState; context.PostReleaseRequestState += PostReleaseRequestState; context.UpdateRequestCache += UpdateRequestCache; context.PostUpdateRequestCache += PostUpdateRequestCache; context.LogRequest += LogRequest; context.PostLogRequest += PostLogRequest; context.EndRequest += EndRequest; context.PreSendRequestHeaders += PreSendRequestHeaders; context.PreSendRequestContent += PreSendRequestContent; context.Error += Error; context.RequestCompleted += RequestCompleted; context.Disposed += AddOnDisposed; } public void Dispose() { } private void Write(object sender,string msg) { HttpApplication application = (HttpApplication)sender; if (application.Context != null) { application.Context.Response.Write("<br>Module1." + msg); } else { utils.log("Module1." + msg + ",Context不存在!", "log.txt"); } } protected void AddOnDisposed(object sender, EventArgs e) { Write(sender, "Disposed"); } protected void BeginRequest(object sender, EventArgs e) { Write(sender, "BeginRequest"); } protected void AuthenticateRequest(object sender, EventArgs e) { Write(sender, "AuthenticateRequest"); } protected void PostAuthenticateRequest(object sender, EventArgs e) { Write(sender, "PostAuthenticateRequest"); } protected void AuthorizeRequest(object sender, EventArgs e) { Write(sender, "AuthorizeRequest"); } protected void PostAuthorizeRequest(object sender, EventArgs e) { Write(sender, "PostAuthorizeRequest"); } protected void ResolveRequestCache(object sender, EventArgs e) { Write(sender, "ResolveRequestCache"); } protected void PostResolveRequestCache(object sender, EventArgs e) { Write(sender, "PostResolveRequestCache"); } protected void MapRequestHandler(object sender, EventArgs e) { Write(sender, "MapRequestHandler"); } protected void PostMapRequestHandler(object sender, EventArgs e) { Write(sender, "PostMapRequestHandler"); } protected void AcquireRequestState(object sender, EventArgs e) { Write(sender, "AcquireRequestState"); } protected void PostAcquireRequestState(object sender, EventArgs e) { Write(sender, "PostAcquireRequestState"); } protected void PreRequestHandlerExecute(object sender, EventArgs e) { Write(sender, "PreRequestHandlerExecute"); } protected void PostRequestHandlerExecute(object sender, EventArgs e) { Write(sender, "PostRequestHandlerExecute"); } protected void ReleaseRequestState(object sender, EventArgs e) { Write(sender, "ReleaseRequestState"); } protected void PostReleaseRequestState(object sender, EventArgs e) { Write(sender, "PostReleaseRequestState"); } protected void UpdateRequestCache(object sender, EventArgs e) { Write(sender, "UpdateRequestCache"); } protected void PostUpdateRequestCache(object sender, EventArgs e) { Write(sender, "PostUpdateRequestCache"); } protected void LogRequest(object sender, EventArgs e) { Write(sender, "LogRequest"); } protected void PostLogRequest(object sender, EventArgs e) { Write(sender, "PostLogRequest"); } protected void EndRequest(object sender, EventArgs e) { Write(sender, "EndRequest"); } protected void PreSendRequestHeaders(object sender, EventArgs e) { Write(sender, "PreSendRequestHeaders"); } protected void PreSendRequestContent(object sender, EventArgs e) { Write(sender, "PreSendRequestContent"); } protected void Error(object sender, EventArgs e) { Write(sender, "Error"); } protected void RequestCompleted(object sender, EventArgs e) { Write(sender, "RequestCompleted"); } }
注册HttpModule
子元素add用来增加一个新的HttpModule;clear将清除前面注册的所有HttpModule;remove移除指定的HttpModule。
name属性由我们自己命名,不一定与类名相同。type属性由分号“,”分为两部分,前面是命名空间及类名,也就是类型名;后面是程序集名。如果我们将代码创建在App_Code目录中,则不需要再指定程序集名。
IIS 7经典模式或之前的版本下,在配置元素system.web下注册HttpModule。
<system.web> <httpModules> <add name="ModuleTest" type="MVC.ModuleTest" /> </httpModules> </system.web>
IIS 7 集成模式下,在配置元素system.webServer下注册HttpModule。注意此时的配置元素名称变为了modules。
<system.webServer> <modules> <add name="Module1" type="MVC.Module1"/> <add name="Module2" type="MVC.Module2"/> </modules> </system.webServer>
兼容:
<system.web> <httpModules> <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"/> </httpModules> </system.web> <system.webServer> <modules> <remove name="ApplicationInsightsWebTracking"/> <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler"/> </modules> </system.webServer>
页面输出
Module2与Module1的实现基本一致。
总结
RequestCompleted 没有在页面上输出,因为RequestCompleted事件触发时,Context已经被不存在了。
在Module1中对AuthenticateRequest事件添加语句
HttpContext.Current.Response.End();
或 HttpContext.Current.ApplicationInstance.CompleteRequest();
主动结束对请求的后续处理。则直接跳过了中间的处理过程。
查看已注册Module
通过HttpApplication.Modules可以遍历当前已注册的Module
var app = ((HttpApplication)sender); var modules = app.Modules; foreach (var key in modules.AllKeys) { app.Context.Response.Write("<br>"+ utils.KeepLength(key,30) + ":" + modules[key].ToString()); }
功能
1、OutputCacheModule完成Asp.net的输出缓存管理工作:
OutputCacheModule的配置参数通过system.web配置元素的caching子元素的outputCache元素进行定义。当启用输出缓存之后(启用还是通过配置文件,下同),OutputCacheModule将注册HttpApplication的ResolveRequestCache和UpdateRequestCache两个事件完成输出缓存的管理。
2、SessionStateModule完成Session的管理工作:
这个Module的配置参数通过配置文件中的system.web配置元素的sessionState子元素进行配置。当启用Session状态管理之后,SessionStateModule将注册HttpApplication的AcquireRequestState、ReleaseRequestState、EndRequest三个事件完成Session状态的管理工作。
3、ProfileModule提供个性化数据管理:
这是一个自定义的类似于Session的会话状态管理,但是,个性化数据的读取和保存可以由程序员完全控制,并且提供了强类型的数据访问方式。这个Module的配置参数在system.web的子元素profile中进行说明。当启用了个性化数据管理之后,Module将注册HttpApplication的AcquireRequestState和EndRequest事件处理。
4、AnonymousIdentificationModule提供匿名用户的标志:
是否启用匿名用户标志在配置文件的system.web配置元素的子元素anonymousIdentification中定义,还可以配置匿名标识的管理方式。由于在AuthenticateRequest事件中将验证用户,获取用户名,所以这个Module注册了PostAuthenticateRequest的事件处理,当用户没有经过验证的时候,为用户分配一个唯一的匿名标识。
5、WindowsAuthenticationModule、FormsAuthenticationModule和PassportAuthenticationModule用来完成用户的验证工作。
它们通过配置文件中system.web的子元素authentication子元素定义,mode属性用来指定网站当前使用的验证方式,也就是哪一个Module将被用来完成验证工作。在启用验证的情况下,FormsAuthenticationModule和PassportAuthenticationModule将注册HttpApplication的AuthenticateRequest和EndRequest事件进行用户的验证处理。WindowsAuthenticationModule将注册AuthenticateRequest的事件处理。
6、RoleManagerModule、UrlAuthorizationModule、FileAuthorizationModule用来完成用户的授权管理:
授权管理的配置参数来自system.web的authorization子元素。UrlAuthorizationModule和FileAuthorizationModule注册了HttpApplication的AuthorizeRequest事件处理,用来检查Url和文件的访问授权。RoleManagerModule在Url和文件访问授权检查通过之后,通过用户的标识和角色来完成用户的授权检查,RoleManagerModule注册了HttpApplication的PostAuthenticateRequest和EndRequest事件处理。
MVC项目中:
OutputCache :System.Web.Caching.OutputCacheModule
Session :System.Web.SessionState.SessionStateModule
WindowsAuthentication :System.Web.Security.WindowsAuthenticationModule
DefaultAuthentication :System.Web.Security.DefaultAuthenticationModule
RoleManager :System.Web.Security.RoleManagerModule
UrlAuthorization :System.Web.Security.UrlAuthorizationModule
FileAuthorization :System.Web.Security.FileAuthorizationModule
AnonymousIdentification :System.Web.Security.AnonymousIdentificationModule
Profile :System.Web.Profile.ProfileModule
UrlMappingsModule :System.Web.UrlMappingsModule
ServiceModel-4.0 :System.ServiceModel.Activation.ServiceHttpModule
UrlRoutingModule-4.0 :System.Web.Routing.UrlRoutingModule
ScriptModule-4.0 :System.Web.Handlers.ScriptModule
__DynamicModule_Microsoft.Owin.Host.SystemWeb.OwinHttpModule, Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35_1ac9b451-2909-4d6b-bbc4-86efa473a0cc :Microsoft.Owin.Host.SystemWeb.OwinHttpModule
__DynamicModule_Microsoft.VisualStudio.Web.PageInspector.Runtime.Tracing.PageInspectorHttpModule, Microsoft.VisualStudio.Web.PageInspector.Runtime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a_203183fe-96d3-43c6-a075-0e794a44e2cf :Microsoft.VisualStudio.Web.PageInspector.Runtime.Tracing.PageInspectorHttpModule
__DynamicModule_System.Web.WebPages.WebPageHttpModule, System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35_875410b2-437d-457c-9dd7-7d6e23a53966 :System.Web.WebPages.WebPageHttpModule
__DynamicModule_System.Web.Optimization.BundleModule, System.Web.Optimization, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35_3d095322-e68f-4d9e-812b-f601d60ca1bf :System.Web.Optimization.BundleModule
Global.asax
可以通过Global.asax文件对HttpApplication的请求处理行为进行快捷定制,甚至有些事件只能通过global.asax来完成定制。在Global.asax中按照“{ModulesName/Application}_{Event_Name}”的方法命名规则进行事件注册。
public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e){} protected void Session_Start(object sender, EventArgs e) { } protected void Application_BeginRequest(object sender, EventArgs e) { } protected void Application_AuthenticateRequest(object sender, EventArgs e) { } protected void Application_Error(object sender, EventArgs e) { } protected void Session_End(object sender, EventArgs e) { } protected void Application_End(object sender, EventArgs e) { } }
特殊的HttpApplication事件处理
Application_Start
只会在第一个HttpApplication对象被创建后调用。
当网站启动后,第一次请求到达网站之后,Asp.net网站将首先触发一次这个事件,而且在网站的整个生命周期中,也仅仅触发一次。
在经典模式下,如果在Application_Start中进行某项初始化操作时发生了异常,网站在第一次被访问时会因为这个异常而报错,但随之的后续页面如果它们的呈现不会受到之前的初始化失败所影响,则可以正常打开,直到某个需要用到这个初始化数据时才会出现报错,报错的延迟会导致调试的困难。
集成模式下,则如果在Application_Start时出错,则后续不管来多少访客,刷新多少此页面,这个报错将始终存在
经典模式下的应对:
private static Exception s_initException; void Application_Start(object sender, EventArgs e) { try { AppInitializer.Init(); } catch( Exception ex ) { // 记下初始化的异常。 s_initException = ex; } } protected void Application_BeginRequest(object sender, EventArgs e) { // 如果存在初始化异常,就抛出来。 // 直到开发人员发现这个异常,并已解决了异常为止。 if( s_initException != null ) throw s_initException; }
Application_End
当网站应用程序被关闭的时候,将触发这个事件。
Session_Start
每个用户访问网站的第一个页面时触发
Session_End
使用了session.abandon(),或session超时用户退出后均可触发.
Application_Error
在出现未处理的错误时触发
protected void Application_Error(object sender, EventArgs e) { var error = Server.GetLastError(); var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500; //如果不是HttpException记录错误信息 if (code != 404) { Exception exception = error.InnerException; Response.Write(exception.Message); //此处邮件或日志记录错误信息 } Response.Write("出错!"); Server.ClearError(); }
在Module中注册自定义事件
版本1
public class TestEventArgs : EventArgs { public string a { get; private set; } public TestEventArgs(string a) { this.a = a; } } public class ModuleTest : IHttpModule { public delegate void TestEventHandler(object sender, TestEventArgs e); public event TestEventHandler Create; public void Dispose() { } public void Init(HttpApplication context) { Create += method; context.BeginRequest += (sender, e)=>{ Create(sender, new TestEventArgs("TestEventArgs.a")); }; } protected void method(object sender, TestEventArgs e) { ((HttpApplication)sender).Context.Response.Write(string.Format("method in ModuleTest,{0}",e.a)); } }
web.config:
<add name="EventModule" type="MVC.ModuleTest"/>
Global:
protected void EventModule_Create(object sender, TestEventArgs e) { Context.Response.Write(string.Format("<br>ModuleTest_Create in Global,{0}", e == null ? "null" : e.a)); }
输出:
- 先执行了Module中定义的方法method,后执行Global中的定义的方法EventModule_Create。
- 第二个方法中的参数TestEventArgs e,和第一个方法中传入的参数是同一个。
版本2
public class ModuleTest2 : IHttpModule { public event EventHandler DoSth; public event EventHandler PostDoSth; public void Dispose() { } public void Init(HttpApplication context) { context.BeginRequest += AddOnBeginRequest; } protected virtual void AddOnBeginRequest(object sender, EventArgs e) { DoSth?.Invoke(sender, e); ((HttpApplication)sender).Context.Response.Write("<br>do sth!"); PostDoSth?.Invoke(sender, e); } }
web.config:
<add name="EventModule2" type="MVC.ModuleTest2"/>
Global:
public class Global : HttpApplication { protected void Application_BeginRequest(object sender, EventArgs e) { Context.Response.Write("<br>Application_BeginRequest in Global"); } protected void EventModule2_DoSth(object sender, EventArgs e) { Context.Response.Write("<br>EventModule2_DoSth in Global"); } protected void EventModule2_PostDoSth(object sender, EventArgs e) { Context.Response.Write("<br>EventModule2_PostDoSth in Global"); } }
输出:
- 同样是对BeginRequest的注入,先执行了Module中的方法,后执行Global中的。
- 通过自定义Module对原有事件进行了扩展。
HttpHandler
对HTTP请求的处理实现在HttpHandler中
自定义接口有:IHttpHandlerFactory,IHttpHandler,IHttpAsyncHandler。自定义后,可在Web.config中注册,建立与请求路径之间的映射关系。
在HttpApplication的PostMapRequestHandler事件中,执行默认的映射操作,调用配置文件,查找用于处理当前请求的HttpHandler。
在HttpContext中提供有RemapHandler 方法, 可以切换当前请求的处理程序,跳过默认映射操作。
使用HttpHandler实现图片防盗链
public class JpgHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { if (context.Request.UrlReferrer == null) { // 如果UrlReferrer为空,则显示一张默认的禁止盗链的图片 WriteImg(context, "/image/error.jpg"); } else { // 如果 UrlReferrer中不包含自己站点主机域名,则显示一张默认的禁止盗链的图片 if (context.Request.UrlReferrer.Host.IndexOf("yourdomain.com") >= 0) { WriteImg(context,context.Request.FilePath); } else { WriteImg(context, "/image/error.jpg"); } } } private void WriteImg(HttpContext context,string path) { string FileName = context.Server.MapPath(path); context.Response.ContentType = "image/JPEG"; context.Response.WriteFile(FileName); } public bool IsReusable { get { return false; } } }
web.config:
<system.web> <httpHandlers> <add name="myJpgHandler" path="*.jpg" verb="*" type="MVC.JpgHandler" /> </httpHandlers> </system.web>
集成管道要配置在 system.webServer/handlers 节下