ASP.NET進階(8):HttpModule和HttpApplication

    前面三節講了控件的構造、呈現和數據綁定,我想該差不多了。本想講一個自定義控件來終結控件部分,但是我個人不太喜歡控件這些東西,所以也就懶的寫相關的內容,抱歉了。雖然我不喜歡使用控件,但我還是喜歡整個WebForm的設計。一個字:“太神了”。前面章節將Page生命週期的時候有朋友評論說內容太少了,今天開始就從來圍繞生命週期的話,講講相關的內容吧。

    IHttpModule是個什麼東西呢?對我們Web開發有什麼用呢?
    先從名字來看他是一個接口,接口就是讓人來繼承的,我們要用它就得繼承他,並實現他的方法。Module的意思是模塊、組件的意思。如果說我們實現了這個接口,並配置了web.config,讓IIS的知道我們的web程序使用了這個組件;那麼我們的程序是不是就比默認的web程序多了個組件?!顯然,而且在必要的時候會調用我們組件裏定義的方法,這就是HttpModule的用處。說白了,就是我們給IIS寫擴展,但該擴展僅僅是針對於使用了(配置config)的web程序。其實每個web應用程序都是一個IIS進程,而這個進程的配置文件就是web.config。
    弄明白了他的意義,我們就開始!我們創建一個Web應用程序,並創建一個類,繼承IHttpModule,並實現他的方法,在Config的Modules節點裏<add name="" type=""/>,OK!

namespace WebApplication1 
{ 
    public class MyHttpModule : IHttpModule 
    { 
        public void Dispose() 
        { 
        }

        public void Init(HttpApplication context) 
        { 
            context.Context.Response.Write(1); 
        } 
    } 
}
<?xml version="1.0"?> 
<configuration> 
  <system.web> 
    <compilation debug="true" targetFramework="4.0" /> 
    <httpModules> 
      <add name="MyHttpModule" type="WebApplication1.MyHttpModule,WebApplication1"/> 
    </httpModules> 
  </system.web>

  <system.webServer> 
    <modules runAllManagedModulesForAllRequests="true"> 
      <add name="MyHttpModule" type="WebApplication1.MyHttpModule,WebApplication1"/><!--注意:type="類的FullName,類所在的dll名"--> 
    </modules> 
  </system.webServer> 
</configuration> 

web.config的配置有2個,上面的那個是給非IIS7用的,下面的顯然就是給IIS7用的。啓動程序,what happend?! 是不是頁的頭部多了個1,有木有!!我們打開任何頁面都會有個1,說明我們的模塊起到作用了,也說明每個請求都會執行HttpModule的Init方法?是不是呢?
我們把代碼改一下:

       public void Init(HttpApplication context) 
        { 
            context.Context.Response.Write(1); 
            context.BeginRequest += OnBeginRequest; 
        }

        public void OnBeginRequest(object sender, EventArgs e) 
        { 
            var app = (HttpApplication)sender; 
            var url = app.Context.Request.RawUrl; 
            app.Context.Response.Write(url); 
        }

分別給Init和OnBeginRequest 兩個方法加斷點,重新編譯下,然後F5看看。Init只走1次,而OnBeginRequest卻走了3次,ur的值l分別是  default.aspx   style.css 和 favorite.ico;可以看出任何url請求,包括靜態文件,都會經過執行我們定義的事件方法!看來這要比只處理aspx慢不少!
Init的必須走一次啊,要不然事件不被訂閱3次了?,但爲什麼只走1次呢?這到底是爲什麼呢? 呵呵,其實很簡單,MyHttpModule就實例化一次哦,實例化後執行Init初始化,然後就駐留在應用程序池了,直到應用程序池被回收,或他被各種原因搞崩潰;而OnBeginRequest是被HttpApplication類的BeginRequest事件訂閱的。事件訂閱是個什麼概念?事件是個特殊的委託,委託是個什麼概念?委託是個方法指針。所以,只要委託被執行,就會執行他指向的方法體,也就是OnBeginRequest,可見OnBeginRequest的執行,是和HttpApplication的BeginRequest有關係的,和MyHttpModule本身已經沒關係了。
    走了3次說明3個Request都執行了BeginRequest,難道每個請求都實例化一個HttpApplication?從名字我就能看出不會的,因爲Application(應用程序)嘛,我們目前運行的就一個,怎麼會不斷的實例化!想刨根問題,徹底整明白,就得翻出Framework的源碼,調試!
(------------聲明,下面的源碼可以不用完全理解,也可以跳過,只要知道跟Request有關就行了------------)
下面來調查下HttpApplication的初始化過程!
用Reflector查閱System.Web名字空間下的類,可以看到HttpApplicationFactory類,他負責HttpApplication的創建。當我們啓動站點後,第一次的時候比較慢,爲什麼呢? 因爲初始化的構建工作。
System.Web.Complilation名字空間下有一堆的構建類,其中就有構建Global.asax的,也就是我們的HttpApplication類,然後緩存到Factory的堆棧裏,我們需要的時候pop出來。 (你可能有疑問,pop了不就沒了嗎? 其實app在執行的時候還會push回去,詳見HttpApplication.ReleaseAppInstance方法)
HttpApplicationFactory有個GetApplicationInstance方法,就是用來獲取HttpApplication的: 

internal static IHttpHandler GetApplicationInstance(HttpContext context) 
{ 
    if (_customApplication != null) 
    { 
        return _customApplication; 
    } 
    if (context.Request.IsDebuggingRequest) 
    { 
        return new HttpDebugHandler(); 
    } 
    _theApplicationFactory.EnsureInited(); 
    _theApplicationFactory.EnsureAppStartCalled(context); 
    return _theApplicationFactory.GetNormalApplicationInstance(context); 
}

private HttpApplication GetNormalApplicationInstance(HttpContext context) 
{ 
    HttpApplication application = null; 
    lock (this._freeList) 
    { 
        if (this._numFreeAppInstances > 0) 
        { 
            application = (HttpApplication) this._freeList.Pop();//如果_freeList裏有,就直接獲取,只有第一次構建的時候沒有 
            this._numFreeAppInstances--; 
            if (this._numFreeAppInstances < this._minFreeAppInstances) 
            { 
                this._minFreeAppInstances = this._numFreeAppInstances; 
            } 
        } 
    } 
    if (application == null) 
    { 
        application = (HttpApplication) HttpRuntime.CreateNonPublicInstance(this._theApplicationType); 
        using (new ApplicationImpersonationContext()) 
        { 
            application.InitInternal(context, this._state, this._eventHandlerMethods);//這裏是初始化application的,並且會經過複雜的一坨代碼後push到_freeList裏 
        } 
    } 
    return application; 
} 

跟蹤這個方法,我們可以斷定,Application是被緩存起來的,不是每次都是實例化的。
通過Reflector的分析,我們能發現這個GetApplicationInstance方法是被HttpRuntime.ProcessRequestNow調用的,終於回到我們的Request來了。

private void ProcessRequestNow(HttpWorkerRequest wr) 
{ 
    HttpContext context; 
    try 
    { 
        context = new HttpContext(wr, false);//實例化上下文 
    } 
    catch 
    { 
        wr.SendStatus(400, "Bad Request"); 
        wr.SendKnownResponseHeader(12, "text/html; charset=utf-8"); 
        byte[] bytes = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>"); 
        wr.SendResponseFromMemory(bytes, bytes.Length); 
        wr.FlushResponse(true); 
        wr.EndOfRequest(); 
        return; 
    } 
    wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, context); 
    Interlocked.Increment(ref this._activeRequestCount); 
    HostingEnvironment.IncrementBusyCount(); 
    try 
    { 
        try 
        { 
            this.EnsureFirstRequestInit(context); 
        } 
        catch 
        { 
            if (!context.Request.IsDebuggingRequest) 
            { 
                throw; 
            } 
        } 
        context.Response.InitResponseWriter();//實例化HttpWriter,輸出用的,我們的控件輸出全靠他 
        IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);//獲取handler也就是httpapplication 
        if (applicationInstance == null) 
        { 
            throw new HttpException(SR.GetString("Unable_create_app_object")); 
        } 
        if (EtwTrace.IsTraceEnabled(5, 1)) 
        { 
            EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, applicationInstance.GetType().FullName, "Start"); 
        } 
        if (applicationInstance is IHttpAsyncHandler)//是否異步的,顯然我們的HttpApplication是繼承這個接口的,所以走這個if 
        { 
            IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance; 
            context.AsyncAppHandler = handler2; 
            handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context);//從BeginRequest就開始執行application的step 
        } 
        else 
        { 
            applicationInstance.ProcessRequest(context);//直接執行 
            this.FinishRequest(context.WorkerRequest, context, null); 
        } 
    } 
    catch (Exception exception) 
    { 
        context.Response.InitResponseWriter(); 
        this.FinishRequest(wr, context, exception); 
    } 
} 

很好,上面的代碼讓我們看清了一個Request的執行過程。而每個Request都會走這個方法!HttpRuntime有個RequestQueue(請求隊列),會依次執行所有的Request。終於知道爲什麼走3次了吧:) 就是application被用了3次。感興趣的同學可以再去跟蹤RequestQueue,我就不貼了。

另外,HttpApplication,意味着他是整個站點的boss,我們定義的myhttpmodule不過是他衆多Modules裏的其中之一。而且我們也可以定義多個module,config裏面add多個就可以了。
在Application初始化的過程中,有初始化module的一段,我貼出來大家看看:

private void InitModules() 
{ 
    this._moduleCollection = RuntimeConfig.GetAppConfig().HttpModules.CreateModules(); 
    this.InitModulesCommon(); 
} 

其中CreateModules就是從web.config的module節點實例化我們配置的module
internal HttpModuleCollection CreateModules() 
{ 
    HttpModuleCollection modules = new HttpModuleCollection(); 
    foreach (HttpModuleAction action in this.Modules) 
    { 
        modules.AddModule(action.Entry.ModuleName, action.Entry.Create()); 
    } 
    modules.AddModule("DefaultAuthentication", new DefaultAuthenticationModule()); 
    return modules; 
} 

最後,初始化所有的module,包括系統的一些module。
private void InitModulesCommon() 
{ 
    int count = this._moduleCollection.Count; 
    for (int i = 0; i < count; i++) 
    { 
        this._currentModuleCollectionKey = this._moduleCollection.GetKey(i); 
        this._moduleCollection[i].Init(this);//初始化每個HttpModule 
    } 
    this._currentModuleCollectionKey = null; 
    this.InitAppLevelCulture(); 
}


(--------------跳到這裏----------------)
    HttpApplication類公開了很多事件。上面的示例程序用到了BeginRequest,這個事件是最先開始執行的事件;其作用很明白,就是Request開始執行時,我們要準備點什麼?或許你可能需要urlrewriter:)。下面插播“事件廣告”,廣告之後馬上飛來。
上面我只是簡單的提了一句“事件是特殊的委託”,並沒有詳細說爲什麼特殊。不知道同學你是否理解事件的意義呢?事件的意義是什麼?
我是這麼理解的。“事件”,代表着一件事情的發生。我們打一個比方,我把每天的生活設計成一個類。那麼,一天的生活包含什麼?包含從早到晚,包含很多事情要去做,甚至包含一些固定的事情。
細品這3個包含:
從早到晚,意味着這是一個過程,從頭到尾,從始到終;很多事情要去做,說明在這個過程中要執行很多事;而一些固定的事情,比如吃飯,睡覺。
我們可以把早和晚,看作是構造函數和析構函數;把很多事情要做看作是事件;把固定的事情看作是方法。
因爲每個人一天的生活都不一定是相同的,所以每天要去做的事我們沒法寫成方法!我們最多隻能定義一些固有的模式的方法抽象,比如起牀後做什麼,午飯後做什麼,睡覺前做什麼。這不就是事件麼?
我們在設計類到時候,如果類的使用有時候也涉及到一些執行過程的問題,而在這個過程中會發生一些未知的事情(未知意味着由外部類來提供,自己提供就是已知了),我們便把這些未知設計成抽象的方法。
由於過程的順序是固定的,比如午飯後做什麼就必須實在午飯後,所以午飯後做什麼事件不能被別人在早上使用(你就是上帝不能把午飯的事情給我挪到早飯,挪了就叫早飯後了)。
同樣的道理,事件的執行不能由外部來決定,這就是事件有別於委託的地方(委託沒有使用限制,隨時隨地都可以用),這也是事件的意義。 整個過程也就是所謂的“生命週期”。
代碼和現實就是這麼的一致,耐人尋味。

廣告回來~~ 繼續看HttpApplication的事件,我把他們按執行的順序貼了出來;從名字就能看出大概的作用。有些我從來沒用過:)
BeginRequest        //請求開始
AuthenticateRequest   
PostAuthenticateRequest   
AuthorizeRequest       
PostAuthorizeRequest
ResolveRequestCache   
PostResolveRequestCache
PostMapRequestHandler
AcquireRequestState    //獲得請求狀態,這時候已經有session了
PostAcquireRequestState
PreRequestHandlerExecute    //準備交給HttpHandler處理
Error            //請求出現了異常!!!
PostRequestHandlerExecute
ReleaseRequestState    //發佈請求的狀態
PostReleaseRequestState
UpdateRequestCache
PostUpdateRequestCache
EndRequest        //結束請求
PreSendRequestHeaders    //準備發送請求頭信息,在這我們還能修改內容
PreSendRequestContent    //準備發送請求內容,這裏就改不了了

這纔是真正的整個生命週期,是不是!而面試題一般考的是Page類的生命週期,這已經過時了,web開發又不光Webform,所以考page類,沒技術含量:)
在HttpApplication裏,把這些事件作爲Step,Step by Step的執行下去,下面是HttpApplication構建Step的代碼:

internal override void BuildSteps(WaitCallback stepCallback) 
{ 
    ArrayList steps = new ArrayList(); 
    HttpApplication app = base._application; 
    bool flag = false; 
    UrlMappingsSection urlMappings = RuntimeConfig.GetConfig().UrlMappings; 
    flag = urlMappings.IsEnabled && (urlMappings.UrlMappings.Count > 0); 
    steps.Add(new HttpApplication.ValidatePathExecutionStep(app)); 
    if (flag) 
    { 
        steps.Add(new HttpApplication.UrlMappingsExecutionStep(app)); 
    } 
    app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps); 
    steps.Add(new HttpApplication.MapHandlerExecutionStep(app)); 
    app.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps); 
    steps.Add(new HttpApplication.CallHandlerExecutionStep(app)); 
    app.CreateEventExecutionSteps(HttpApplication.EventPostRequestHandlerExecute, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventReleaseRequestState, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventPostReleaseRequestState, steps); 
    steps.Add(new HttpApplication.CallFilterExecutionStep(app)); 
    app.CreateEventExecutionSteps(HttpApplication.EventUpdateRequestCache, steps); 
    app.CreateEventExecutionSteps(HttpApplication.EventPostUpdateRequestCache, steps); 
    this._endRequestStepIndex = steps.Count; 
    app.CreateEventExecutionSteps(HttpApplication.EventEndRequest, steps); 
    steps.Add(new HttpApplication.NoopExecutionStep()); 
    this._execSteps = new HttpApplication.IExecutionStep[steps.Count]; 
    steps.CopyTo(this._execSteps); 
    this._resumeStepsWaitCallback = stepCallback; 
} 

從構建的順序我們也能看出執行的順序,每個Step都有一個Execute的方法,挨個執行下去,如果程序出現異常,則直接跳出。而我們的Page執行是在CallHandlerExecutionStep這個Step裏。

    好啦,就講到這唄?今天漢字比較少,代碼比較多;您還有啥不能明白的就評論裏聊吧。沒用過的同學動寫個吧,HttpModule是個好東西哦。PS:我們公司面試的筆試題裏有道題“給一個正在運行的網站增加一個異常監控功能,該怎麼實現?” 你能想到怎麼做嗎?:)

發佈了42 篇原創文章 · 獲贊 191 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章