Asp.net 深度

 

 

 

 

 

 

現在我們可以整理一下完整的一個HTTP請求在ASP.NET Framework下是如何被處理的:

HttpRequestinetinfo.exeASPNET_ISAPI.dllHttp PipelineASPNET_WP.exeHttpRuntimeHttpApplication FactoryHttpApplicationHttpModuleHttpHandler FactoryHttpHandlerHttpHandler.ProcessRequest()

 

比如如果你想要中途截獲一個Http Request並且做些自己的處理,該如何做呢?通過仔細的對上面圖示的觀察,讀者應當知道能夠在什麼地方截獲到這個HTTP請求。對,就是在HTTPRuntime運行時內部來做到這一點的,確切的說,是在HttpModule這個容器中做到這一點的。既然已經進入了神奇的HttpRuntime內部世界,我們當然要多看幾眼了,接下來我們會繼續我們的歷險步伐,向HttpModule容器進軍!

我們已經知道HttpModule容器是一個HTTP請求的必經之路,而我們在此小節的任務就是在HttpModule的內部深入看個究竟。

HttpModuleASP.NET Framework中的位置

我們上回說到,一個來自於客戶端的HTTP請求被截獲後經過層層轉交(怎麼都在踢皮球?呵呵)到達了HttpModule這個“請求監聽器”。HttpModule就類似於安插在ASPNET_WP.EXE

進程中的一個竊聽器,稍微有些常識的人都會很自然的想象得到竊聽器是用來做什麼的,而我們的HttpModule可以說是作竊聽器的絕好人選了,但是需要明確的是,HttpModule絕對不是簡單的監聽器,它可以做到更多的東西,比如它可以對截獲的請求增加一些內容等等。那麼HttpModule在整個ASP.NET Framework中的位置處在哪裏呢?下面我們通過圖示來看看:

另外需要明白的是,當一個HTTP請求到達HttpModule的時候,整個ASP.NET Framework系統還並沒有對這個HTTP請求做任何的真正處理,也就是說此時對於HTTP請求來講,HttpModule只是它路過的一個地方而以。但是正是因爲HttpModule是一個HTTP請求的“必經之路”,所以我們可以在這個HTTP請求傳遞到真正的請求處理中心(HttpHandler)之前附加一些我們需要的信息在這個HTTP請求信息之上,或者針對我們截獲的這個HTTP請求信息作一些額外的工作,或者在某些情況下乾脆終止滿足一些條件的HTTP請求,從而可以起到一個Filter過濾器的作用,而不僅僅是一個竊聽器了。 通過查閱MSDN(不要去相信.NET SDK自帶的那個QuickStarts Web文檔,正式版本中竟然在很多地方沒有更新這個文檔,很多東西在正式版本是無效的),你會發現系統HttpModule實現了一個叫做IHttpModule的接口,很自然的就應當想到,只要我們自己的類能夠實現IHttpModule接口,不就可以完全替代系統的HttpModule了嗎?完全正確。     

在我們開始自己的HttpModule類之前,我先來告訴你係統中的那個HttpModule是什麼樣子的,ASP.NET系統中默認的HttpModule有以下幾個:

             System.Web.Caching.OutputCacheModule

             System.Web.SessionState.SessionStateModule

             System.Web.Security.WindowsAuthenticationModule

             System.Web.Security.FormsAuthenticationModule

             System.Web.Security.PassportAuthenticationModule

             System.Web.Security.UrlAuthorizationModule

             System.Web.Security.FileAuthorizationModule

這些系統默認的HttpModule是在文件machine.config中配置,這個文件位於你安裝的.NET框架所在的目錄中,比如在你的系統文件目錄中的C:/WINNT/Microsoft.NET/Framework/v1.0.3705/CONFIG/machine.config。在我們開發ASP.NET 應用程序的時候會頻繁的使用到一個web.config配置文件,那麼這個machine.config和我們常見的web.config有什麼關係呢?原來在ASP.NET Framework啓動處理一個Http Request的時候,她會依次加載machine.config以及你請求頁面所在目錄的web.config文件,裏面的配置是有<remove>標籤的,什麼意思不說也知道了吧。如果你在machine.config中配置了一個自己的HttpModule,你仍然可以在離你最近web.config文件中“remove”掉這個映射關係。

 

構建我們自己的HttpModule 

在上一小節中,我們談到了系統默認的各個HttpModule均繼承實現了一個叫做IhttpModule的接口。我們先來看看這個接口的真實面目吧:

語法:

public interface IHttpModule

需求:

名稱空間: System.Web

平臺: Windows 2000, Windows XP Professional, Windows .NET Server family

裝配件: System.Web (in System.Web.dll)

公共成員方法:

void Dispose();

參數:無

返回值:void

作用:銷燬不再被module所使用的資源。

void Init(HttpApplication context);

參數:HttpApplication類型的實例

返回值:void

作用:初始化一個module,爲捕獲HTTP請求做出一些準備。

 

瞭解了接口IhttpModule的方法,我們也就知道了如何去實現它了。

接下來,我們來開始我們自己的HttpModule構建歷程吧。

1)打開VS.NET新建一個“Class Library”項目,將它命名爲MyHttpModule

2)引用System.Web.dll文件

在代碼區域敲入:

using System;

using System.Web;

 

namespace MyHttpModuleTest

{

    /// <summary>

    /// 說明:用來實現自定義HttpModule的類

    /// 作者:uestc95

    /// 聯繫:[email protected]

    /// </summary>

public class MyHttpModule:IhttpModule

{

        /// <summary>

        /// 說明:構造器方法

        /// 作者:uestc95

        /// 聯繫:[email protected]

        /// </summary>

        public MyHttpModule()

        {

            

        }

 

        /// <summary>

        /// 說明:實現IHttpModule接口的Init方法

        ///作者:uestc95

        ///聯繫:[email protected]

        /// </summary>

        /// <param name="application">HttpApplication類型的參數</param>

        public void Init(HttpApplication application)

        {

         application.BeginRequest +=new EventHandler(this.Application_BeginRequest);

         application.EndRequest +=new EventHandler(this.Application_EndRequest);

        } 

        /// <summary>

        /// 說明:自己定義的用來做點事情的私有方法

        /// 作者:uestc95

        /// 聯繫:[email protected]

        /// </summary>

        /// <param name="obj">傳遞進來的對象參數</param>

        /// <param name="e">事件參數</param>

        private void Application_BeginRequest(Object obj,EventArgs e)

        {

                   //聲明HttpApplication

                HttpApplication application=(HttpApplication)obj;

            HttpContext context=application.Context;

            HttpResponse response=context.Response;

            HttpRequest request=context.Request; 

            response.Write("我來自Application_BeginRequest,:)");

        }

         /// <summary>

        /// 說明:自己定義的用來做點事情的私有方法

        /// 作者:uestc95

        /// 聯繫:[email protected]

        /// </summary>

        /// <param name="obj">傳遞進來的對象參數</param>

        /// <param name="e">事件參數</param>

        private void Application_EndRequest(Object obj,EventArgs e)

        {

            HttpApplication application=(HttpApplication)obj;

            HttpContext context=application.Context;

            HttpResponse response=context.Response;

            HttpRequest request=context.Request; 

            response.Write("我來自Application_EndRequest,:)";

        }

        /// <summary>

        /// 說明:實現IHttpModule接口的Dispose方法

        /// 作者:uestc95

        ///聯繫:[email protected]

        /// </summary>

        public void Dispose(){}

    }

}

 

3)在VS.NET中編譯之後,你會得到MyHttpModule.dll這個文件。

4)接下來我們的工作就是如何讓ASPNET_WP.exe進程將http request交給我們自己寫的這個HttpModule呢?方法就是配置web.config文件。

web.config文件中增加如下幾句話:

<httpModules>

    <add name="test" type="MyHttpModuleTest.MyHttpModule,MyHttpModule"/>

</httpModules>

注意要區分大小寫,因爲web.config作爲一個XML文件是大小寫敏感的。“type=MyHttpModuleTest.MyHttpModule,MyHttpModule”告訴我們,系統將會將HTTP請求交給位於MyHttpModule.dll文件中的MyHttpModuleTest.MyHttpModule類去處理。而這個DLL文件系統將會自動到/bin子目錄或者系統全局程序集緩衝區(GAC)搜尋。我們可以將我們剛纔得到的DLL文件放在bin子目錄中,至於後者,你可以通過.NET SDK正式版自帶的Config工具做到,我們不詳細說了。

好了,我們的用來截獲HTTP請求的自定義HttpModule就完成並且裝配完成了,你可以試着在你的web項目中建立一個新的WebForm,運行看看呢?:)

最後,我們假設一個使用這個HttpModule的場合。A站點提供免費的ASP.NET虛擬空間給大家,但是A站點的管理者並不想提供免費的午餐,他想要在每一個頁面被瀏覽的時候自動彈出自己公司的廣告,我總不能時刻監視所有用戶的所有頁面吧,並且想要在每一個頁面手動添加一段JS代碼,工作量之大是不可想象的,也是非常不現實的。那麼好了,只要我們的HttpModule一旦被掛接完成,這一切都將是輕而易舉的事情了,只要我們在每一個HTTP請求被我們捕獲的時候,給他在HTTP請求信息上面附加上一些我們自己的寫的JavaScript代碼就好了!

我們上面提到在Init()方法中使用了兩個事件BeginRequestEndRequest,這兩個事件分別是Init()中可以處理的所有事件的最開始事件和最終事件,在他們中間還有一些其它的事件可以被我們利用,可以查閱MSDN,這一點我們會在下面詳細的講解。

在下一節開始之前,我們請各位讀者考慮這樣一個問題:在HttpModule中可以正常使用Response,Request,Server,Application對象嗎?請讀者自行試着寫一些代碼看看行不行。還有一個問題就是,在HttpModule中能操作Session對象嗎?如果可以或者不可以,請仔細思考一下原因何在,在下一節我們會詳細的探討這個問題。

 在這一小節中,我們會詳細的探討有關HttpModule的運行機制,從而使得各位讀者能夠透徹的瞭解HttpModule是如何控制HTTP請求的。

我們在上一節曾經提及當一個HTTP請求被ASP.NET Framework捕獲之後會依次交給HttpModule以及HttpHandler來處理,但是需要明確的是,不能理解爲HttpModuleHttpHandler是完全獨立的。實際上是,在HTTP請求在HttpModule傳遞的過程中會在某個事件內將控制權交給HttpHandler的,而真正的處理在HttpHandler中執行完成之後,HttpHandler會再次將控制權交還給HttpModule。也就是說HttpModule在某個請求經過她的時候會在恰當時候同HttpHandler進行通信,在何時,如何通信呢?這就是下面提到的了。

我們在上一小節提到在HttpModule容器中最開始的事件是BeginRequest,最終的事件是EndRequest。你如果仔細看上次給出的源程序的話,應當發現在方法Init()中參數我們傳遞的是一個HttpApplication類型,而我們曾經提及的兩個事件正是這個傳遞進來的HttpApplication的事件之一。

 HttpApplication還有其它衆多的事件,分別如下:

            application.BeginRequest事件

            application.EndRequest事件

            application.PreRequestHandlerExecute事件

            application.PostRequestHandlerExecute 事件

            application.ReleaseRequestState事件

            application.AcquireRequestState事件

            application.AuthenticateRequest事件

            application.AuthorizeRequest事件

            application.ResolveRequestCache事件

application.UpdateRequestCache事件

            application.PreSendRequestHeaders事件

            application.PreSendRequestContent事件

 接下來我們來看看這些事件的含義解釋:

 HttpApplication事件名稱                 什麼時間觸發

 BeginRequest事件                           處理HTTP請求開始之前觸發

 AuthenticateRequest事件                  驗證客戶端時候觸發

 AuthenticateRequest事件                  執行存取的時候觸發

 ResolveRequestCache事件                從緩存中得到相應時候觸發

 AcquireRequestState事件                加載初始化Session時候觸發

 PreRequestHandlerExecute事件        HTTP請求送入HttpHandler之前觸發

 PostRequestHandlerExecute事件       HTTP請求送入HttpHandler之後觸發

 ReleaseRequestState事件                  存儲Session狀態時候觸發

UpdateRequestCache事件                 更新緩存信息的時候觸發

 EndRequest事件                              HTTP請求處理完成時候觸發

 PreSendRequestHeaders事件            在向客戶端發送Header之前觸發

 PreSendRequestContent事件             在向客戶端發送內容之前觸發

需要注意的是,在事件EndRequest之後還會繼續執行application.PreSendRequestHeaders事件以及application.PreSendRequestContent事件,而這兩個事件大家想必應當從名稱上面看得出來事做什麼用途的了吧。是的,一旦觸發了這兩個事件,就表明整個對一個HTTP請求的處理已經執行完成了,在這兩個事件中是開始向客戶端傳送處理完成的數據流了。細心的讀者看到這裏,會有一個疑問:怎麼沒見到進入HttpHandler容器就處理完成了?不是提到過HttpHandler容器纔是真正處理HTTP請求的嗎?

其實一開始我們就提到了,在一個HTTP請求在HttpModule容器的傳遞過程中,會在某一個時刻(確切的說應當是事件)中將這個HTTP請求傳遞給HttpHandler容器的。這個事件就是ResolveRequestCache,在這個事件之後,HttpModule容器會建立一個HttpHandler的入口實例(做好準備了,:)),但是此時並沒有將HTTP請求的控制權交出,而是繼續觸發AcquireRequestState事件以及PreRequestHandlerExecute事件(如果你實現了的話)。看到了嗎,最後一個事件的前綴是Pre,這表明下一步就要進入HttpHandler容器了,事實上的確如此,正如我們猜想的那樣,在PreRequestHandlerExecute事件之後,HttpModule容器就會將控制權暫時交給HttpHandler容器,以便進行真正的HTTP請求處理工作。

而在HttpHandler容器內部會執行ProcessRequest方法來處理HTTP請求。在容器HttpHandler處理完畢整個HTTP請求之後,會將控制權交還給HttpModuleHttpModule則會繼續對處理完畢的HTTP請求信息流(此時的HTTP請求信息流已經是被處理過後了的信息流了)進行層層的轉交動作,直到返回到客戶端爲止。

怎麼樣,是不是有些混亂?下面是一個完整的HttpModule的生命週期示意步驟:

        Http Request開始

HttpModule

               

       HttpModule.BeginRequest()

               

       HttpModule.AuthenticateRequest()

                

      HttpModule.AuthorizeRequest()                             

HttpModule.ResolveRequestCache()

               

   建立HttpHandler控制點

                         

接着處理(HttpHandler已經建立,此後Session可用)

               

  HttpModule.AcquireRequestState()

 HttpModule.PreRequestHandlerExecute()

 

進入HttpHandler處理HttpRequest

    HttpHandler.ProcessRequest()

返回到HttpModule接着處理(HttpHandler生命週期結束,Session失效)

    HttpModule.PostRequestHandlerExecute()

    HttpModule.ReleaseRequestState()

    HttpModule.UpdateRequestCache()

    HttpModule.EndRequest()

    HttpModule.PreSendRequestHeaders()

    HttpModule.PreSendRequestContent()

將處理後的數據返回客戶端

整個Http Request處理結束

 

 爲了更加形象的描述整個HttpModuleHTTP請求處理週期,接下來我們來看看

HTTP請求在整個HttpModule中的生命週期圖:

 

 

 

 

 

 

想必讀者應當可以從上面的HttpModule生命週期圖中找到了上一小節我們提出的那個問題:Session對象在何種情況下可以正常的使用!爲了驗證上面的流程,我們可以用下面的這個自己的HttpModuel來驗證一下就知道了。

注意我們下面給出的是類的內容,代碼框架還是上一小節我們給出的那個,自己加上就好了:

        public void Init(HttpApplication application) 

        { 

       application.BeginRequest += (new EventHandler(this.Application_BeginRequest));

       application.EndRequest += (new EventHandler(this.Application_EndRequest));

application.PreRequestHandlerExecute +=(new EventHandler(this.Application_PreRequestHandlerExecute));

application.PostRequestHandlerExecute  +=(new EventHandler(this.Application_PostRequestHandlerExecute));

   application.ReleaseRequestState  +=(new EventHandler(this.Application_ReleaseRequestState));

application.AcquireRequestState +=(new EventHandler(this.Application_AcquireRequestState));

application.AuthenticateRequest +=(new EventHandler(this.Application_AuthenticateRequest));

application.AuthorizeRequest +=(new EventHandler(this.Application_AuthorizeRequest));

application.ResolveRequestCache +=(new EventHandler(this.Application_ResolveRequestCache));

application.PreSendRequestHeaders +=(new EventHandler(this.Application_PreSendRequestHeaders));

application.PreSendRequestContent +=(new EventHandler(this.Application_PreSendRequestContent));

        }

        private void Application_PreRequestHandlerExecute(Object source, EventArgs e)

        {

            HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context;

            context.Response.Write("Application_PreRequestHandlerExecute<br>");

        }

         private void Application_BeginRequest(Object source, EventArgs e) 

        {

            HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context;

             context.Response.Write("Application_BeginRequest<br>");

        }

        private void Application_EndRequest(Object source, EventArgs e) 

        {

            HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context;

            context.Response.Write("Application_EndRequest<br>");

         }        

        private void Application_PostRequestHandlerExecute(Object source,EventArgs e)

        {

            HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context;

            context.Response.Write("Application_PostRequestHandlerExecute<br>");

         } 

        private void Application_ReleaseRequestState(Object source, EventArgs e)

        {

            HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context; 

            context.Response.Write("Application_ReleaseRequestState<br>"); 

        } 

        private void Application_UpdateRequestCache(Object source, EventArgs e)

        {

            HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context; 

            context.Response.Write("Application_UpdateRequestCache<br>"); 

        } 

        private void Application_AuthenticateRequest(Object source, EventArgs e)

        {

            HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context; 

            context.Response.Write("Application_AuthenticateRequest<br>"); 

        } 

        private void Application_AuthorizeRequest(Object source, EventArgs e)

        {

            HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context; 

            context.Response.Write("Application_AuthorizeRequest<br>");

}

        private void Application_ResolveRequestCache(Object source, EventArgs e)

        {

            HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context; 

            context.Response.Write("Application_ResolveRequestCache<br>"); 

        } 

        private void Application_AcquireRequestState(Object source, EventArgs e)

        {

            HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context; 

            context.Response.Write("Application_AcquireRequestState<br>"); 

        } 

        private void Application_PreSendRequestHeaders(Object source, EventArgs e)

        {

            HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context; 

            context.Response.Write("Application_PreSendRequestHeaders<br>"); 

        } 

        private void Application_PreSendRequestContent(Object source, EventArgs e)

        {

    HttpApplication application = (HttpApplication)source;

            HttpContext context = application.Context;

            context.Response.Write("Application_PreSendRequestContent<br>"); 

        }

        public void Dispose()

        {

        } 

將上面的代碼編譯成爲一個類庫文件,再按照上一小節給出的方法將它掛接到ASP.NET Framework系統,你運行此演示代碼就可以看到和我們給出的HttpModule生命週期圖是完全吻合的。

現在我們來思考這樣一個問題:如果我同時編寫了多個自定義的HttpModule,並且也都裝配了他們,ASP.NET Framework會怎樣加載這多個自定義的HttpModule呢?下面我們就一同通過一個完整的例子來演示看看:我們在下面的代碼中會建立兩個HttpModule類庫DLL文件,並且同時裝配他們在web.config中。

1)、在VS.NET中建立一個名爲IhttpModule的類庫項目

2)、引入名稱空間文件System.Web.dll

3)、在代碼區域輸入如下的代碼:

using System;

using System.Web; 

using System.Collections;

 

public class HelloWorldModule : IHttpModule

{

 public String ModuleName 

 { 

  get { return "HelloWorldModule"; } 

 }    

 

 public void Init(HttpApplication application) 

 { 

  application.BeginRequest += (new EventHandler(this.Application_BeginRequest));

  application.EndRequest += (new EventHandler(this.Application_EndRequest));

application.PreRequestHandlerExecute +=(new EventHandler(this.Application_PreRequestHandlerExecute));

application.PostRequestHandlerExecute  +=(new EventHandler(this.Application_PostRequestHandlerExecute));

application.ReleaseRequestState  +=(new EventHandler(this.Application_ReleaseRequestState));

application.AcquireRequestState +=(new EventHandler(this.Application_AcquireRequestState));

application.AuthenticateRequest +=(new EventHandler(this.Application_AuthenticateRequest));

application.AuthorizeRequest +=(new EventHandler(this.Application_AuthorizeRequest));

application.ResolveRequestCache +=(new EventHandler(this.Application_ResolveRequestCache));

application.PreSendRequestHeaders +=(new EventHandler(this.Application_PreSendRequestHeaders));

application.PreSendRequestContent +=(new EventHandler(this.Application_PreSendRequestContent)); 

 }     

 //Your BeginRequest event handler.

 private void Application_BeginRequest(Object source, EventArgs e) 

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule: Beginning of Request<br>");

 }

    

 //Your EndRequest event handler.

 private void Application_EndRequest(Object source, EventArgs e) 

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context;

context.Response.Write("HelloWorldModule: End of Request<br>");

 }           

 private void Application_PostRequestHandlerExecute(Object source,EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule:Application_PostRequestHandlerExecute:<br>");    

 } 

 private void Application_ReleaseRequestState(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

 context.Response.Write("HelloWorldModule:Application_ReleaseRequestState :<br>");

 }

 

 /// <summary>

 /// 

 /// </summary>

 /// <param name="source"></param>

 /// <param name="e"></param>

 private void Application_PreRequestHandlerExecute(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context;

  HttpResponse Response=context.Response;

  HttpRequest Request=context.Request;

context.Response.Write("HelloWorldModule:Application_PreRequestHandlerExecute :<br>"); 

 }

 private void Application_UpdateRequestCache(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule:Application_UpdateRequestCach :<br>");

 }

 

 private void Application_AuthenticateRequest(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context;     

context.Response.Write("HelloWorldModule:Application_AuthenticateRequest<br>");

 }

 private void Application_AuthorizeRequest(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

context.Response.Write("HelloWorldModule:Application_AuthorizeRequest<br>");

 }

 

 private void Application_ResolveRequestCache(Object source, EventArgs e)

{

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule:Application_ResolveRequestCache<br>");

 }

 

 private void Application_AcquireRequestState(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

context.Response.Write("HelloWorldModule:Application_AcquireRequestState<br>");

 }

 

 private void Application_PreSendRequestHeaders(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule:Application_PreSendRequestHeaders<br>"); 

 }

 

 private void Application_PreSendRequestContent(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

HttpContext context = application.Context;

context.Response.Write("HelloWorldModule:Application_PreSendRequestContent<br>");

 }

 public void Dispose() 

 {

 }

}

 

3)、編譯這個項目之後會得到一個叫做IhttpModule.dll的文件

 

接下來我們再來共同建立另外一個很類似的自定義HttpModule類庫項目

1)、在VS.NET中新建一個名爲IhttpModule2的項目

2)、引入System.Web.dll文件

3)、在代碼區域輸入:

 

using System;

using System.Web; 

using System.Collections;

 

public class HelloWorldModule2 : IHttpModule

{

 public String ModuleName 

 { 

  get { return "HelloWorldModule"; } 

 }    

 

 public void Init(HttpApplication application) 

 { 

  application.BeginRequest += (new EventHandler(this.Application_BeginRequest));

  application.EndRequest += (new EventHandler(this.Application_EndRequest));

  application.PreRequestHandlerExecute +=(new EventHandler(this.Application_PreRequestHandlerExecute));

  application.PostRequestHandlerExecute  +=(new EventHandler(this.Application_PostRequestHandlerExecute));

  application.ReleaseRequestState  +=(new EventHandler(this.Application_ReleaseRequestState));

  application.AcquireRequestState +=(new EventHandler(this.Application_AcquireRequestState));

  application.AuthenticateRequest +=(new EventHandler(this.Application_AuthenticateRequest));

  application.AuthorizeRequest +=(new EventHandler(this.Application_AuthorizeRequest));

  application.ResolveRequestCache +=(new EventHandler(this.Application_ResolveRequestCache));

  application.PreSendRequestHeaders +=(new EventHandler(this.Application_PreSendRequestHeaders));

  application.PreSendRequestContent +=(new EventHandler(this.Application_PreSendRequestContent)); 

 }

 //Your BeginRequest event handler.

 private void Application_BeginRequest(Object source, EventArgs e) 

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule2: Beginning of Reques<br>");

 }

    

 //Your EndRequest event handler.

 private void Application_EndRequest(Object source, EventArgs e) 

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule2: End of Request<br>");

 }        

 

 private void Application_PostRequestHandlerExecute(Object source,EventArgs e)

{

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule2:Application_PostRequestHandlerExecute:<br>");

 }

 

 private void Application_ReleaseRequestState(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context;

  context.Response.Write("HelloWorldModule2:Application_ReleaseRequestState :<br>");

 }

 

 /// <summary>

 /// 

 /// </summary>

 /// <param name="source"></param>

 /// <param name="e"></param>

 private void Application_PreRequestHandlerExecute(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context;

  HttpResponse Response=context.Response;

  HttpRequest Request=context.Request; 

context.Response.Write("HelloWorldModule2:Application_PreRequestHandlerExecute :<br>");

 }

 

 private void Application_UpdateRequestCache(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule2:Application_UpdateRequestCach :<br>");

 }

 

 private void Application_AuthenticateRequest(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule2:Application_AuthenticateRequest<br>");

 }

 

 private void Application_AuthorizeRequest(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule2:Application_AuthorizeRequest<br>");

 }

 

 private void Application_ResolveRequestCache(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule2:Application_ResolveRequestCache<br>");

 }

private void Application_AcquireRequestState(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule2:Application_AcquireRequestState<br>");

 }

 

 private void Application_PreSendRequestHeaders(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule2:Application_PreSendRequestHeaders<br>"); 

 }

 

 private void Application_PreSendRequestContent(Object source, EventArgs e)

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context; 

  context.Response.Write("HelloWorldModule2:Application_PreSendRequestContent<br>");

 }

 public void Dispose() 

 {

 }

}

編譯這個自定義的HttpModule類庫項目之後,會得到一個名爲IhttpModule2.dll的文件。

 

下面我們需要來建立一個Web應用程序項目來測試這兩個自定義的HttpModule文件的效果如何。

1)、在VS.NET中建立一個Web應用程序項目名字叫做:4.2.3

2)、在文件web.config中增加如下語句:

        <httpModules>

            <add name="test" type="HelloWorldModule,IHttpModule"/>

            <add name="test2" type="HelloWorldModule2,IHttpModule2"/>

        </httpModules>

      以便通知ASP.NET Framework

3)、將上面我們生成的兩個自定義HttpModule文件拷貝到此Web應用程序的bin/子目錄中。

4)、直接運行這個Web

應用程序,我們會得到如下的結果:

 從上面的運行結果我們可以看到,我們在web.config文件中引入自定義HttpModule的順序就決定了多個自定義

HttpModule在處理一個HTTP請求的接管順序。而系統默認那幾個HttpModule是最先被ASP.NET Framework

所加載上去的。

最後,我們來這樣一個問題:在前面小節中我們曾經提到過我們可以利用HttpModule來實現當滿足某一條件的時候終止此次的HTTP請求,那麼如何做到在我們的HttpModule中終止一個HTTP請求呢?方法就是調用HttpApplication.CompleteRequest()方法。並且我們知道在一個HttpModule中首先觸發的事件將是BeginRequest,這裏也就理所當然的成爲了我們終止一個HTTP請求的地方了。

我們可以更改一下BeginRequest事件處理代碼如下:

 private void Application_BeginRequest(Object source, EventArgs e) 

 {

  HttpApplication application = (HttpApplication)source;

  HttpContext context = application.Context;

  application.CompleteRequest();

  context.Response.StatusCode=500;

  context.Response.StatusDescription=Internal Server Error!;

 //context.Response.Write("HelloWorldModule1: Beginning of Request<br>");

 } 

這樣,就會終止一個HTTP請求了。但是需要注意的是,即使調用了HttpApplication.CompleteRequest()方法終止了一個

HTTP請求,ASP.NET Framework仍然會觸發HttpApplication後面的這三個事件:EndRequest事件、PreSendRequestHeaders

事件、PreSendRequestContent事件。

如果多個自定義的HttpModule存在的話,比如上面我們給出的例子:IhttpModule.dll以及IhttpModule2.dll。如果我們在

IhttpModule.dll中終止了一個HTTP請求的話,這個HTTP請求也不會再觸發IhttpModule2.dll中的諸如BeginRequest

等等事件了,但是仍然會觸發IhttpModule.dll以及IhttpModule2.dllEndRequest事件、PreSendRequestHeaders事件、

PreSendRequestContent事件。

我們可以看看下面的示意圖:

現在我們已經在HttpModule的神祕世界歷險了一番,相信各個讀者對於深奧的HttpModule不再陌生了,接下來我們將繼續在ASP.NET Framework的世界裏歷險,下一站的目的地是神祕的IhttpHandler,也就是HTTP請求處理中心了!

4.3ASP.NET  Framework深度歷險 --初次接觸神祕的IHttpHandler

我們在上一小節在HttpModule的世界歷險的時候就提到過,HttpHandler是一個HTTP請求的真正處理中心,也正是在這個HttpHandler容器當中,ASP.NET Framework才真正的對客戶端請求的服務器頁面做出編譯和執行,並將處理過後的信息附加在HTTP請求信息流中再次返回到HttpModule中。在本小節,我們將會一同來初步瞭解一下神祕的HttpHandler

4.3.1  IHttpHandler是什麼?

我們一直在使用HttpHandler,而不是IHttpHandler,請不要混淆這兩者。前者只是我們用來描述使用的一個名稱而已,而後者則是ASP.NET Framework中一個重要的接口的名稱,而我們歷險的重點自然是在IHttpHandler

 

 IHttpHandlerASP.NET Framework提供的一個接口,它定義瞭如果要實現一個HTTP請求的處理所需要必需實現的一些系統約定。也就是說,如果你想要自行處理某種類型的HTTP請求信息流的話,你需要實現這些系統約定才能夠做到,這也正是接口在.NET中的作用。

我們先來看看IHttpHandler的真實面目吧:

語法:

public interface IHttpHandler

需求:

名稱空間: System.Web

平臺: Windows 2000, Windows XP Professional, Windows .NET Server family

裝配件: System.Web (in System.Web.dll)

成員字段:

IsReusable      

成員方法:

void ProcessRequest(HttpContext context); 

 HttpHandlerHttpModule類似,系統都默認提供了很多的系統HttpHandler類,用來處理不同的HTTP請求。 

同樣的,這些默認的系統HttpHandler類也和系統的HttpModule一樣在machine.config文件中進行配置的,下面我們就來仔細看看了:

在文件machine.config中可以看到下面的配置段:

<httpHandlers>

 <add verb="*" path="trace.axd" type="System.Web.Handlers.TraceHandler"/>

 <add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>

 <add verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory"/>

 <add verb="*" path="*.asmx" type="System.Web.Services.Protocols.WebServiceHandlerFactory,

System.Web.Services, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" validate="false"/> 

 <add verb="*" path="*.rem" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFacto

ry, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>

 <add verb="*" path="*.soap" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>

 <add verb="*" path="*.asax" type="System.Web.HttpForbiddenHandler"/>

 <add verb="*" path="*.ascx" type="System.Web.HttpForbiddenHandler"/>

<add verb="*" path="*.config" type="System.Web.HttpForbiddenHandler"/>

 <add verb="*" path="*.cs" type="System.Web.HttpForbiddenHandler"/>

 <add verb="*" path="*.csproj" type="System.Web.HttpForbiddenHandler"/>

 <add verb="*" path="*.vb" type="System.Web.HttpForbiddenHandler"/>

 <add verb="*" path="*.vbproj" type="System.Web.HttpForbiddenHandler"/>

 <add verb="*" path="*.webinfo" type="System.Web.HttpForbiddenHandler"/>

 <add verb="*" path="*.asp" type="System.Web.HttpForbiddenHandler"/>

 <add verb="*" path="*.licx" type="System.Web.HttpForbiddenHandler"/>

 <add verb="*" path="*.resx" type="System.Web.HttpForbiddenHandler"/>

 <add verb="*" path="*.resources" type="System.Web.HttpForbiddenHandler"/>

 <add verb="GET,HEAD" path="*" type="System.Web.StaticFileHandler"/>

 <add verb="*" path="*" type="System.Web.HttpMethodNotAllowedHandler"/>

</httpHandlers> 

可以看到,ASP.NET FrameworkASP.NET應用程序的正常運行提供了很多的系統默認HttpHandler類,用來適應不同類型的HTTP請求。比如,我們最熟悉的*.aspx文件,用來處理此類型的HTTP請求,ASP.NET Framework將會交給一個名爲System.Web.UI.PageHandlerFactoryHttpHandler類來處理。

我們在這裏來講解一下<httpHandlers>配置代碼的用法:

主配置標籤語法:

<httpHandlers>

   <add verb="verb list" path="path/wildcard" type="type,assemblyname"  validate="" />

   <remove verb="verb list" path="path/wildcard" />

   <clear />

</httpHandlers> 

子配置標籤語法:

<add>:詳細指定需要的動詞(GET/HEAD等等)以及相應的請求文件路徑。其中的路徑文件支持通配符*

<remove>:移除一個到HttpHandler上的映射。並且不支持通配符。

<clear>:移除所有的到HttpHandler上的映射。

HttpHandlerHttpModule一樣,系統會在最初始由ASP.NET Framework首先加載machine.config中的系統HttpHandler

,而後會加載Web應用程序所在目錄的web.config中的用戶自定義的HttpHandler類。

 

但是同HttpModule不同的是:一旦定義了自己的HttpHandler類,那麼它對系統的HttpHandler的關係將是“覆蓋”關係的,也就是說如果我們自己定義了一個對*.aspx請求的自定義HttpHandler類的話,那麼系統將會將對此HTTP請求的處理權限完全交給我們自己定義的這個HttpHandler類來處理,而我們的HttpHandler類則需要完全的解析這個HTTP請求,並做出處理,如果不這樣的話,那麼,這個HTTP請求將會被空HTTP信息流所代替,或者被我們自定義的HttpHandler類中的信息數據流所代替。這一點我們會在後面的章節中詳細探討。我們在這裏僅僅給出一個系統默認HttpHandler和我們的自定義HttpHandler之間的關係圖:

                          

在下一小節中,我們將來探討一下IHttpHandler是如何處理一個HTTP請求的。

 

4.3.2 IHttpHandler如何處理HTTP請求

 在上一小節中我們初步介紹了IHttpHandler的知識,接下來我們會探討一下當一個HTTP請求被傳遞到一個HttpHandler

容器之後,HttpHandler容器是如何對這個HTTP請求進行解析和處理的。

 

我們在上面小節中可以知道,一個HttpHandler容器是實現了一個名爲IHttpHandler的接口,而我們也瞭解了IHttpHandler

接口需要實現的描述。我們能注意到,在IHttpHandler中最爲重要的一個成員方法就是:

ProcessRequest。從字面上面我們可以很容易的得知,這個方法就是HttpHandler用來處理一個HTTP請求的,實事的確如此。當一個HTTP請求經由HttpModule容器傳遞到HttpHandler容器中的時候,ASP.NET Framework會調用HttpHandlerProcessRequest成員方法來對這個HTTP請求做真正的處理,我們以一個ASPX頁面來講,正是在這裏一個ASPX頁面才被系統處理解析,並將處理完成的結果繼續(注意:這裏是繼續,也就是說仍在一個HttpModule的生命週期內)經由HttpModule傳遞下去,直至到達客戶端。

我們從4.3.1小節中的配置文件中可以瞭解到對於ASPX頁面,ASP.NET Framework在默認情況下是交給System.Web.UI.PageHandlerFactory這個HttpHandlerFactory來處理的,需要注意的這裏並不是我們一直在討論的HttpHandler容器,

而是一個HttpHandler工廠,這一點我們會在下面詳細討論,這裏我們僅僅簡單的介紹一下什麼是HttpHandler工廠。所謂一個HttpHandlerFactory,是指當一個HTTP請求到達這個HttpHandler工廠的時候,HttpHandlerFactory會提供出一個HttpHandler容器,交由這個HttpHandler容器來處理這個HTTP請求,這種處理方式我們在第三章中的Factory設計模式提到過,這樣做的好處是大大減輕了系統的壓力,提高了系統的適應性和靈活度。

但不論怎樣,一個HTTP請求都是最終交給一個HttpHandler容器中的ProcessRequest方法來處理的。因此我們在下面的小節中將會深入的探討一下IHttpHandler

4.4ASP.NET  Framework深度歷險– IHttpHandler深入

 4.3小節,我們瞭解到了IHttpHandler是如何來處理一個HTTP請求的,在本小節我們會更加深入討論有關

IHttpHandler的一些技術細節。

在本小節內,我們先來共同實現一個簡單的HttpHandler容器,以便能對HttpHandler容器有一個感性的認識。

 

通過實現 IHttpHandler 接口我們可創建自定義 HTTP 處理程序,而該接口只包含兩個方法。通過調用 IsReusableIHttpHandlerFactory可查詢處理程序以確定是否可使用同一實例爲多個請求提供服務。ProcessRequest 方法將 HttpContext 實例用作參數,這使它能夠訪問 Request Response 內部對象。

 

我們來看看下面的這個例子:

1)在VS.NET中建立一個新的“類庫”項目,命名爲:MyHandler

2)在代碼區域鍵入如下代碼:

using System;

using System.Web;

 

namespace  MyNamespace

{

///<summary>

///目的:實現簡單的自定義HttpHandler容器

///</summary>

public class  MyHandler : IHttpHandler

{ 

public void ProcessRequest(HttpContext context) 

 {

  HttpResponse Response=context.Response;

  HttpRequest Request=context.Request;

  Response.Write("<h1><b>Hello world!</b></h1>");

 }

 

public bool IsReusable

 {

  get{return true;}

 } 

}

}

3)編譯這個項目,我們得到了MyHandler.dll這個文件。

4)將這個MyHandler.dll文件拷貝到我們的測試Web應用程序目錄中的/bin子目錄。

5)配置我們的測試Web應用程序配置文件Web.Config如下:

在配置文件Web.Config中增加如下信息:

<httpHandlers>

  <add verb="*" path="*" type="MyNamespace.MyHandler,MyHandler"/>

</httpHandlers> 

其中的type屬性以逗號分隔開的分別是:自定義HttpHandler中的類名稱以及最終的程序集文件(也就是我們編譯得到的那個DLL文件)。

而其中的path屬性則表示我們的自定義HttpHandler將會接管何種類型的文件請求。

 

這樣,經過上面的配置之後,對於所有類型的頁面文件HTTP請求都將會被我們的自定義HttpHandler捕獲。而我們的自定義HttpHandler則忽略了所有的HTTP請求,替代輸出的是一段經典的“Hello world!”語句。

 

讀者可以試着運行這段例子,我們給出的這個簡單例子運行結果如下:

                          Hello word

這樣,一個我們自定義的HttpHandler容器就完全運行的和我們料想的那樣一樣了,也最終證實了一個HTTP請求的真正處理中心是在一個HttpHandler容器中的。

從上面的例子代碼中我們可以看到,ProcessRequest方法傳遞進來的是一個HttpContext對象,而我們通過這個HttpContext

對象可以方便的使用我們平常在ASP.NET頁面中經常直接使用的Response對象以及Request對象,那麼我們能夠使用Session對象嗎?如果你只是簡單的類似使用Response對象那樣直接在我們的自定義HttpHandler容器中使用一個Session對象的話,編譯器將會編譯出錯。

在一個HttpHandler容器中如果需要訪問會話狀態對象Session,你必須實現一個IRequiresSessionState接口。 IRequiresSessionState接口指定目標 HTTP 處理程序接口具有對會話狀態值的讀寫訪問權限。這只是一個標記接口,沒有任何方法。

IRequiresSessionState 接口的語法如下:

命名空間: System.Web.SessionState

平臺: Windows 2000, Windows XP Professional, Windows .NET Server family

程序集: System.Web ( System.Web.dll ) 

 

也就是說我們只有在我們的程序中實現了這個接口,纔有權利訪問Session對象。這個接口和其他的普通接口不太一樣的是,他沒有任何方法,僅僅起到一個標記的作用而已。

我們修改我們的程序如下,以便能夠在我們的自定義HttpHandler容器中存取Session對象:

using System;

using System.Web;

using System.Web.SessionState;

namespace MyNamespace

{

///<summary>

///目的:實現簡單的自定義HttpHandler容器

///</summary>

public class MyHandler : IHttpHandler ,IRequiresSessionState

{ 

public void ProcessRequest(HttpContext context) 

 {

   //聲明我們自己的Response對象

   HttpResponse Response=context.Response; 

   //聲明我們自己的Request對象

   HttpRequest Request=context.Request;

   //聲明我們自己的Session對象

   HttpSessionState Session=context.Session; 

   //打印一段話

   Response.Write("<h1><b>Hello world!</b></h1><br>"); 

   //Session對象賦值

   Session["test"]="this is a session test"; 

   //打印測試Session對象的值 

 Response.Write("<h1><b>Session[/"test/"]="+Session["test"].ToString()+"</b></h1>");

 }

 

public bool IsReusable

 {

  get{return true;}

 } 

}

}

 

這樣,在我們的自定義HttpHandler容器中就可以正常存取Session對象了。

 

重新編譯這個項目,將生成的MyHandler.dll文件覆蓋到我們剛纔的測試Web應用程序的/bin子目錄中,再次運行測試Web應用程序我們會得到如下結果:

在上面我們曾經提到過,ASP.NET Framework實際上並不是直接將相關的頁面資源HTTP請求定位到一個其內部默認的IHttpHandler容器之上的,而是定位到了其內部默認的IHttpHandler工廠上了。

 

這個IHttpHandler工廠的作用就是對很多的系統已經實現了的IHttpHandler容器進行調度和管理的。這樣做的優點是大大增強了系統的負荷處理能力,能夠很好的提升效率。

 

接下來,我們就看看IHttpHandler工廠和IHttpHandler容器之間的關係吧。

 

首先我們來了解一下IHttpHandler工廠的語法定義:

 

命名空間: System.Web

平臺: Windows 2000, Windows XP Professional, Windows .NET Server family

程序集: System.Web ( System.Web.dll ) 

公共方法:

1IHttpHandler GetHandler(HttpContext context,string requestType,string url,string pathTranslated);

作用:返回實現 IHttpHandler 接口的類的實例。 

參數:

context HttpContext 類的實例,它提供對用於爲 HTTP 請求提供服務的內部服務器對象(如

 RequestResponseSession Server)的引用。

requestType :客戶端使用的 HTTP 數據傳輸方法(GET POST)。 

url :所請求資源的 RawUrl

pathTranslated :所請求資源的 PhysicalApplicationPath 

 

返回值:處理請求的新的 IHttpHandler 對象。

 

 2ReleaseHandler  

作用:使工廠可以重用現有的處理程序實例。

 

4.5ASP.NET  Framework深度歷險 IHttpModule 以及 IHttpHandler應用實例

 

 

 

 

4.6ASP.NET  Framework深度歷險 深入ASP.NET事件模型機制

 

 ASP.NET相對於以前古老的ASP來講,一個很重要的革命性技術就是ASP.NET是完全基於事件驅動的一種技術,而這點是ASP是不可比擬的,深入的探索ASP.NET的事件模型機制是本章的內容。

4.6.1 ASP.NET事件模型初步認識

  ASP.NET之所以對於以前的ASP是一個革命性的鉅變,在很大程度上面是由於ASP.NET技術是一種完全基於事件驅動的全新技術,如果你以前曾經接觸過Delphi或者VB,你一定就會了解事件驅動技術是怎樣的一種技術了。但是需要特別提醒讀者注意的事,ASP.NET的事件模型機制以不同於一般的Client/Server時代的事件模型機制。在Client/Server時代,所有的事件捕捉和觸發以及處理都是交給客戶端的應用程序來實現的,而服務端則用來執行復雜的耗費時間的計算,從而也決定了Client/Server

時代的事件模型機制的相對簡單和容易實現。

 ASP.NET是完全基於HTTP協議的,而HTTP協議是一種無連接的Web協議,也就是說每當一次客戶端的Http請求處理結束之後,服務器端就會自動和客戶端斷開連接。從而我們應當可以知道,ASP.NET的事件驅動是和Client/Server時代的事件驅動有所不同的一種基於HTTP協議的技術,在ASP.NET中事件的觸發和事件的處理是分別在客戶端和服務端進行的。一個事件在客戶端被觸發之後,會通過HTTP協議以POST的方式發送到服務器端,而服務器端則通過ASP.NET 頁面架構(ASP.NET page framework)來進行相應的處理和反饋。

說到這裏,我們來回憶一下曾經寫過的一些ASP.NET程序,如果你仔細留意的話,應當會發現,每一個頁面在我們使用

VS.NET生成的時候,會自動的將所有的Web Control以及相應的標籤放置到一個默認的<Form>當中,並且默認的傳遞方式是POST,也可以是GET方式。你可以試着移去或者更改這個<Form>標籤,但是當你重新編譯並運行這個頁面的時候,你應當發現系統仍然自動的將這個<Form>標籤更改爲自己需要的了,這也就驗證了上面我們剛剛提到的ASP.NET需要客戶端以POST/GET的方式通過HTTP協議將事件信息發送到服務器端,從而服務器端才能處理這些事件。

  ASP.NET頁面架構在服務器端接收到這些來自客戶端的事件信息之後,會自動的判別並決定調用相應的方法來進行事件處理。我們通過下圖可以清晰的瞭解到這一點:

從上圖我們可以看到,ASP.NET Framework負責了從客戶端事件捕獲、傳遞、事件信息解釋的全部過程,從另外一個方面來說,開發ASP.NET應用程序的時候,你不必親自去管理這些事件模型的技術細節,而是可以將更大的時間和精力投入到商業邏輯的分析設計當中去,節約了大量的時間。當然由於事件的整個處理過程屬於ASP.NET的核心技術,我們也可能去強行的干預到。由於ASP.NET爲我們做了所有的事件管理和處理工作,我們就可以像往常的普通應用程序那樣來自如的編寫各種事件的響應代碼了。 

4.6.2 ASP.NET的事件模型深入瞭解

在上一節我們初步的瞭解了一下ASP.NET事件模型機制的知識,在這一節我們來詳細的深入進去看看ASP.NET事件驅動模型的內部到底是怎樣的一回事。

從本質上面來講,ASP.NET的事件模型就是Web Forms的事件模型,我們先來看下面的一個例子代碼片斷:

 (如果沒有特殊指明我們的例子代碼均採用代碼綁定機制)

頁面代碼

WebForm1.aspx

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="WebApplication1.WebForm1" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

<HTML>

 <HEAD>

  <title>WebForm1</title>

  <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">

  <meta name="CODE_LANGUAGE" Content="C#">

  <meta name="vs_defaultClientScript" content="JavaScript">

  <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">

 </HEAD>

 <body MS_POSITIONING="GridLayout">

  <form id="Form1" method="post" runat="server">

   <asp:Button id="Button1" style="Z-INDEX: 101; LEFT: 243px; POSITION: absolute; TOP: 114px" runat="server" Text="

件模型測試"></asp:Button>

  </form>

 </body>

</HTML>

 

CodeBehind代碼

WebForm1.aspx.cs

 

using System;

using System.Collections;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Web;

using System.Web.SessionState;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.HtmlControls;

namespace WebApplication1

{

 /// <summary>

 /// Summary description for WebForm1.

 /// </summary>

 public class WebForm1 : System.Web.UI.Page

 {

  protected System.Web.UI.WebControls.Button Button1;

  private void Page_Load(object sender, System.EventArgs e)

  {

   // Put user code to initialize the page here

  }

 

  #region Web Form Designer generated code

  override protected void OnInit(EventArgs e)

  {

   //

   // CODEGEN: This call is required by the ASP.NET Web Form Designer.

   //

InitializeComponent();

    base.OnInit(e);

  }

  

  /// <summary>

  /// Required method for Designer support - do not modify

  /// the contents of this method with the code editor.

  /// </summary>

  private void InitializeComponent()

  {    

   this.Button1.Click += new System.EventHandler(this.Button1_Click);

   this.Load += new System.EventHandler(this.Page_Load);

  }

  #endregion

  private void Button1_Click(object sender, System.EventArgs e)

  {

   Response.Write("Hello,world!");

  }

 }

}

 

上面的一段最簡單的代碼就是在頁面打印一句古老的“Hello,world!”。

上面的最簡單的代碼運行結果是在頁面上面打印一句最簡單的“Hello,world!”。仔細的查閱上面的CodeBehind

代碼,我們可以簡單的瞭解到如下幾點:

1ASP.NET Framework是通過如下方式來調用事件處理方法的:

this.Button1.Click += new System.EventHandler(this.Button1_Click);

 2、事件Button1_Click的參數共有兩個:

   Button1_Click(object sender, System.EventArgs e) 

 

雖然我們給出的例子相當的簡單,但是事件驅動的模型機制卻是完全一致的,下面就然我們來由淺入深的逐步的深入到

ASP.NET的事件模型機制的內部看一看。

 

 ASP.NET的所有的事件處理方法都只有相同的兩個參數:object類型參數以及System.EventArgs類型的參數。注意,我們提到的所有的事件處理方法都只有這兩個相同的參數,唯一會有區別的是,其他的某些事件處理方法的參數2會是System.EventArgs的一個子類,以便適應特殊的處理要求。

在此我們再一次強調一下,ASP.NET技術是完全構建在.NET框架技術之上的,它的所有的技術細節均來源於.NET框架。正如剛纔我們看到的事件處理方法的兩個參數,他們都是一個類(Class),因而你應當不難理解剛纔所講的“其他的某些事件處理方法的參數2會是System.EventArgs的一個子類”,也就是在某些情況下,參數會是繼承於System.EventArgs類的一個子類。

 

或許有些讀者看到這裏會有些疑問,爲什麼所有的事件處理方法都必須要有相同的參數呢?原因還在我們一再強調的那句話“ASP.NET技術是完全構建在.NET框架技術之上的”。

 

在我們給出的代碼中,是通過代表(Delegate)的機制來將事件和事件處理方法綁定在一起的,而代表(Delegate)則是.NET

框架的一個機制。在ASP.NET中需要通過.NET提供的EventHandler來做到這個綁定的:

[Serializable]

public delegate void EventHandler(object senderEventArgs e); 

由此我們可以清楚的瞭解到爲什麼所有的ASP.NET事件處理方法都需要有相同的兩個參數了,因爲它們都是通過EventHandler來實現事件和處理方法的綁定的。

 

在我們繼續前進之前,我們先來透徹的瞭解一下事件和代表之間的關係。

一個事件(Event)是一個對象發送的一個消息,用來表示一個動作發生了。而一個動作可以被用戶操作或者其他程序所觸發。觸發事件的對象被事件發送者(Event Sender)調用;捕獲處理事件的對象被事件接收者(Event Receiver)調用。

在事件通訊當中,事件的發送者並不知道哪個對象或者方法將要去接收/處理髮送過去的事件。因而在事件源和事件接收者之間就需要一箇中間人存在,這一點類似於“指針”。而.NET架構專門定義了一個特殊的類型用來提供一個指向函數的指針,就是“代表”,也叫做“委託”。 

一個代表就是一個類(class),他提供了一個面向某個方法的引用參考。和其他一般普通的類不一樣,一個代表類擁有一個簽名(Signature),從而使得代表能夠提供面向符合自己簽名的方法引用參考。所謂“簽名”指的是:一個函數方法的參數個數、參數類型的集合。因此,一個代表也可以被認爲是一個類型安全的函數指針或者回調函數。當然,代表並不僅僅是爲事件本身而設計的,他還有其他更爲強大的功用,而在這裏我們僅僅探討事件捕獲類型的代表。

下面的例子簡單的告訴我們一個事件代表是如何定義的:

// AlarmEventHandler

是一個警報事件的代表

// AlarmEventArgs

是用來捕獲來自於警報事件的數據的代表類,他繼承於父類

//EventArgs.

public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);

 

從上面的代碼我們可以看到,一個代表的定義語法和我們一般的函數定義很類似,但是關鍵字delegate會通知編譯器這是一個代表類型。

.NET框架中,約定事件代表(Event Delegate)擁有兩個參數,一個表示事件源,另外一個表示事件的數據。

 

一個代表的定義提供代表的簽名,CLR(common language runtime)會使用這個簽名。

 

一個代表的實例可以被綁定到任何一個符合代表簽名的方法上,比如:

 

public class WakeMeUp 

{

// AlarmRang has the same signature as AlarmEventHandler.

public void AlarmRang(object sender, AlarmEventArgs e){...};

...

}

 

下面是將事件綁定到相應事件處理方法的代碼:

 

WakeMeUp w = new WakeMeUp();

AlarmEventHandler alhandler = new AlarmEventHandler(w.AlarmRang);

 

這樣,在alhandler被觸發的時候,它將會依次調用方法w.AlarmRang

 

至於更爲詳細代表機制和技術細節則不屬於本書的探討範圍之內了,讀者可以自行查閱相關的技術資料來了解。

 

現在我們應當可以理解這句代碼的含義了:

 

 this.Button1.Click += new System.EventHandler(this.Button1_Click);

 

這句代碼表示將事件this.Button1.Click綁定在方法this.Button1_Click上面了,也就是說一旦觸發按鈕的Click事件,服務器端將會自動的調用方法Button1_Click來處理。

 

ASP.NET Framework提供給我們的服務器端事件並不是很多,比如我們在ASP時代時常用到的OnMouseOver等等事件均沒有提供,這是什麼原因呢?其實從上面我們的講解當中就應當瞭解到,在ASP.NET Framework下,事件驅動模型機制的實現是在客戶端和服務端分別實現的,之間需要通過HTTP協議方式來傳遞事件信息,因而如果頻繁的觸發各類事件會對整個Web站點產生很大的流量壓力,比如剛剛提到的OnMouseOver事件,每次微小的鼠標移動都將會觸發它,這對於服務器端的壓力是非常大的,因而系統並沒有提供。這些特殊的需要的事件我們需要在客戶端自動的實現處理,從而大大減輕服務器端的壓力,也帶來的更多的靈活性。

 

接下來我們來談談事件的觸發順序,也許讀者會有這樣的疑問:事件的觸發順序還有必要來討論嗎?我們平常的事件觸發不都是自然的有自己的順序的嗎?比如吃飯,我當然要先觸發“張開嘴”這個事件,再觸發“送入食物”這個事件了。

 

我們平常生活中的事件觸發的確如此,但是在ASP.NET的世界當中卻不是這樣的,正如我們上面提到的,如果頻繁的和服務器端進行事件信息的傳遞會大大降低服務器的處理效率和性能,因而有些事件比如OnMouseOverASP.NET中並沒有提供,但是有些事件雖然也會頻繁的觸發但是必須提供,比如很多情況下要用到的Change事件。對於這種情況,ASP.NET Framework提供了一個折衷的辦法,就是對於這類事件在觸發的時候,並不是立即將事件信息發送到服務器,而是緩存在客戶端,等到再一次的事件信息被髮送到服務器端的時候一同發送回去。因此,這些緩存着的事件以及剛剛被觸發的事件在服務器端被接收到的時候,ASP.NET Framework並不會按照特定的順序去解釋執行處理這些事件。

 

下面我們來通過一個實際的例子來驗證這一點:

頁面文件:

WebForm1.aspx

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="WebApplication1.WebForm1" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

<HTML>

 <HEAD>

  <title>WebForm1</title>

  <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">

  <meta name="CODE_LANGUAGE" Content="C#">

  <meta name="vs_defaultClientScript" content="JavaScript">

  <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">

 </HEAD>

 <body MS_POSITIONING="GridLayout">

  <form id="Form1" method="post" runat="server">

   <asp:Button id="Button1" style="Z-INDEX: 101; LEFT: 233px; POSITION: absolute; TOP: 302px" runat="server" Text="

事件模型測試"></asp:Button>

   <asp:DropDownList id="DropDownList1" style="Z-INDEX: 102; LEFT: 257px; POSITION: absolute; TOP: 171px" runat="server">

    <asp:ListItem>Item 1</asp:ListItem>

    <asp:ListItem>Item 2</asp:ListItem>

    <asp:ListItem>Item 3</asp:ListItem>

    <asp:ListItem>Item 4</asp:ListItem>

    <asp:ListItem>Item 5</asp:ListItem>

    <asp:ListItem>Item 6</asp:ListItem>

   </asp:DropDownList>

   <asp:TextBox id="TextBox1" style="Z-INDEX: 103; LEFT: 210px; POSITION: absolute; TOP: 219px" runat="server">我首先改變這個</asp:TextBox>

  </form>

 </body>

</HTML>

 

CodeBehind文件:

WebForm1.aspx.cs

using System;

using System.Collections;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Web;

using System.Web.SessionState;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.HtmlControls;

 

namespace WebApplication1

{

 /// <summary>

 /// 測試事件觸發處理順序

 /// </summary>

 public class WebForm1 : System.Web.UI.Page

 {

  protected System.Web.UI.WebControls.DropDownList DropDownList1;

  protected System.Web.UI.WebControls.TextBox TextBox1;

  protected System.Web.UI.WebControls.Button Button1;

 

  private void Page_Load(object sender, System.EventArgs e)

  {

   // Put user code to initialize the page here

  }

 

  #region Web Form Designer generated code

  override protected void OnInit(EventArgs e)

  {

   //

   // CODEGEN: This call is required by the ASP.NET Web Form Designer.

   //

   InitializeComponent();

   base.OnInit(e);

  }

  

  /// <summary>

  /// Required method for Designer support - do not modify

  /// the contents of this method with the code editor.

  /// </summary>

  private void InitializeComponent()

  {    

   this.Button1.Click += new System.EventHandler(this.Button1_Click);

   this.DropDownList1.SelectedIndexChanged += new System.EventHandler(this.DropDownList1_SelectedIndexChanged);

   this.TextBox1.TextChanged += new System.EventHandler(this.TextBox1_TextChanged);

   this.Load += new System.EventHandler(this.Page_Load); 

  }

  #endregion

 

  private void Button1_Click(object sender, System.EventArgs e)

  {

   Response.Write("Hello,world!<br>");

  }

 

  private void DropDownList1_SelectedIndexChanged(object sender, System.EventArgs e)

  {

   Response.Write("DropDownList Change事件被觸發了!<br>");

  }

 

  private void TextBox1_TextChanged(object sender, System.EventArgs e)

  {

   Response.Write("TextBox1_TextChanged事件被觸發了!<br>");

  }

 }

}

 

在上面的例子中,我們在頁面上面放置了一個下拉框以及一個TextBox文本框,我們首先改變TextBox中的文字,在改變下拉框的選項,我們可以看到這兩個Change事件並沒有立即觸發POST動作,而是被客戶端暫時緩存了,當我們點擊測試按鈕的時候,才刷新整個頁面,也就是提交給了服務器端進行消息處理,但是處理的順序並不是我們剛纔進行的那樣,可以看到下面的結果:

讀者可以試着自己將上面的代碼拷貝到VS.NET中運行一下看看結果怎樣。

4.7ASP.NET  Framework深度歷險 深入ASP.NET狀態管理模型

 

4.8ASP.NET  Framework深度歷險 深入ASP.NET安全管理模型

 

4.9ASP.NET  Framework深度歷險 利用MSMQSOAP實現分佈式應用系統

 

4.10ASP.NET  Framework深度歷險 ASP.NET中的設計模式(Design Pattern)應用

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章