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

來源:互聯網
上載者:User

    前面三節講了控制項的構造、呈現和資料繫結,我想該差不多了。本想講一個自訂控制項來終結控制項部分,但是我個人不太喜歡控制項這些東西,所以也就懶的寫相關的內容,抱歉了。雖然我不喜歡使用控制項,但我還是喜歡整個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:我們公司面試的筆試題裡有道題“給一個正在啟動並執行網站增加一個異常監控功能,該怎麼實現?” 你能想到怎麼做嗎?:)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.