IIS/asp.net管道


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. 引入集成管道

  1. HTTP.sys監聽攔截客戶端請求開始處理。
  2. HTTP.sys通過配置信息聯系WAS獲取相關信息。
  3. WAS 向配置存儲中心請求配置信息。applicationHost.config。
  4. WWW 服務接受到配置信息(應用程序池配置信息,站點配置信息等),使用配置信息去配置 HTTP.sys 處理策略。
  5. WAS為這個請求對應的應用程序池(Application Pool)開啟W3WP Worker Process。
  6. W3WP Worker Process處理以后,將Response返回給HTTP.sys。
  7. 客戶端接受到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,在我們的頁面中可以直接通過它取得原始的請求信息。

.net網站的文件上傳讀取進度條和斷點下載

經典模式下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)
{
    string UserName = HttpContext.Current.User.Identity.Name;
}

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

輸出:

  1. 先執行了Module中定義的方法method,后執行Global中的定義的方法EventModule_Create。
  2. 第二個方法中的參數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");
    }
}

輸出:

  1. 同樣是對BeginRequest的注入,先執行了Module中的方法,后執行Global中的。
  2. 通過自定義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 節下

 

ASP.NET網站中其他的初始化方法

ASP.NET管道生命周期詳解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM