精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

用ASP.NET寫自己的服務框架

開發 后端
本文將分享作者認為ASP.NET的另一些重要的內容拿出來與大家一起分享,同時將使用本次所講述的內容改進上篇博客所演示的那個簡單的服務,讓它成為一個真正能用的服務框架。

  我的上篇博客【我心目中的ASP.NET核心對象】講述了一些我認為在ASP.NET中比較重要的核心對象,以及演示了直接使用它們也能實現一個簡單的服務響應。今天,我將繼續把我認為ASP.NET的另一些重要的內容拿出來與大家一起分享,同時將使用本次所講述的內容改進上篇博客所演示的那個簡單的服務,讓它成為一個真正能用的服務框架。

  在這篇博客中,不僅會繼續演示上次提到的三個核心對象,我還會再引入另二個關鍵對象,我將用更多實戰代碼來演示它們如何在一起協同工作,來完成許多常見任務,展現它們的精彩故事,也最終會讓它們來實現我的服務框架。因此,這篇博客不僅僅是針對ASP.NET的基礎技術的講觸,而是更多地以實戰形式展示這些核心對象所能發揮的強大功能,以一個不到700行的輕量級服務框架來顯示它們的核心價值,才是這篇博客的目標。

  首先我要談的話題是ASP.NET的請求處理【管線】,我認為這是ASP.NET中最重要的內容了,所有到達ASP.NET的請求都要經過管線來處理,不管是WebForms, MVC, WebService, WCF(ASP.NET的承載方式),還是其它微軟的采用HTTP協議的框架。為什么這些框架都選擇要ASP.NET做為它們的運行平臺呢?

  我們可以考慮一下:如果讓您從無到有設計一個服務框架,有哪些事件是必須要處理的?

  我想有三個最根本的事件要做:1. 監聽請求端口,2. 為每個傳入的連接請求分配線程來執行具體的響應操作,3. 要把請求的數據讀出來,并負責將處理后的響應數據發送給調用者。

  這其實是個比較復雜也很枯燥的過程,但每個服務器端程序都需要這些基本功能。幸好IIS和ASP.NET可以為我們做好這些事情,所以那些框架選擇ASP.NET平臺就可以省去這些復雜的任務。使用ASP.NET平臺不僅可以簡化設計,它還有著良好的擴展性以滿足更多的框架在這個平臺上面繼續開發,而這個良好擴展性是離不開它的請求處理管線的。

  ASP.NET是一個功能完善的平臺框架,它既提供一些高層次的框架供我們使用,比如:WebForms, MVC, WebService,也提供一些低層次的機制讓我們使用,以便于讓我們開發有特殊要求的新框架,新解決方案。這個低層次的機制就是請求處理管線,使用這個管線的有二類對象:HttpHandler, HttpModule,控制這條管線工作的對象是:HttpApplication 。通常情況下,我們并不需要直接使用HttpApplication對象,因此本文的主題將主要介紹HttpHandler, HttpModule這二類對象的功能以及如何使用它們。

  理解ASP.NET管線

  管線(Pipeline)這個詞也是很有點意思,這個詞也形象地說明了每個ASP.NET請求的處理過程:請求是在一個管道中,要經過一系列的過程點,這些過程點連接起來也就形成一條線。以上是我對于這個詞的理解,如果有誤,懇請給予指正。這些一系列的過程點,其實就是由HttpApplication引發的一系列事件,通??梢杂蒆ttpModule來訂閱,也可以在Global.asax中訂閱,這一系列的事件也就構成了一次請求的生命周期。

  事件模式,也就是觀察者模式。根據【C# 3.0 設計模式】一書中的定義:“觀察者模式定義了對象之間的一種聯系,使得當一個對象改變狀態時,所有其它的對象都可以相應地被通知到。" ASP.NET的管線設計正是采用了這種方式,在這個設計模式中,觀察者就是許多HttpModule對象,被觀察的對象就是每個”請求“,它的狀態是由HttpApplication控制,用于描述當前請求的處理階段,HttpApplication會根據一個特定的順序修改這個狀態,并在每個狀態改變后引發相應的事件。 ASP.NET會為每個請求分配一個HttpApplication對象來引發這些事件,因此可以讓一大批觀察者了解每個請求的狀態,每個觀察者也可以在感興趣的時候修改請求的一些數據。這些與請求相關的數據的也就是我上篇博客中提到的HttpRequest, HttpResponse。正是由于引入了事件機制,ASP.NET框架也有了極強的擴展能力。再來看看管線處理請求的過程,我將直接引用MSDN中的原文【IIS 5.0 和 6.0 的 ASP.NET 應用程序生命周期概述】中的片段。

  在處理該請求時將由 HttpApplication 類執行以下事件。 希望擴展 HttpApplication 類的開發人員尤其需要注意這些事件。

  1. 對請求進行驗證,將檢查瀏覽器發送的信息,并確定其是否包含潛在惡意標記。 有關更多信息,請參見 ValidateRequest 和腳本侵入概述。

  2. 如果已在 Web.config 文件的 UrlMappingsSection 節中配置了任何 URL,則執行 URL 映射。

  3. 引發 BeginRequest 事件。

  4. 引發 AuthenticateRequest 事件。

  5. 引發 PostAuthenticateRequest 事件。

  6. 引發 AuthorizeRequest 事件。

  7. 引發 PostAuthorizeRequest 事件。

  8. 引發 ResolveRequestCache 事件。

  9. 引發 PostResolveRequestCache 事件。

  10. 根據所請求資源的文件擴展名(在應用程序的配置文件中映射),選擇實現 IHttpHandler 的類,對請求進行處理。 如果該請求針對從 Page 類派生的對象(頁),并且需要對該頁進行編譯,則 ASP.NET 會在創建該頁的實例之前對其進行編譯。

  11. 引發 PostMapRequestHandler 事件。

  12. 引發 AcquireRequestState 事件。

  13. 引發 PostAcquireRequestState 事件。

  14. 引發 PreRequestHandlerExecute 事件。

  15. 為該請求調用合適的 IHttpHandler 類的 ProcessRequest 方法(或異步版 IHttpAsyncHandler.BeginProcessRequest)。 例如,如果該請求針對某頁,則當前的頁實例將處理該請求。

  16. 引發 PostRequestHandlerExecute 事件。

  17. 引發 ReleaseRequestState 事件。

  18. 引發 PostReleaseRequestState 事件。

  19. 如果定義了 Filter 屬性,則執行響應篩選。

  20. 引發 UpdateRequestCache 事件。

  21. 引發 PostUpdateRequestCache 事件。

  22. 引發 EndRequest 事件。

  23. 引發 PreSendRequestHeaders 事件。

  24. 引發 PreSendRequestContent 事件。

  如果是IIS7,第10個事件也就是MapRequestHandler事件,而且在EndRequest 事件前,還增加了另二個事件:LogRequest 和 PostLogRequest 事件。

  只有當應用程序在 IIS 7.0 集成模式下運行,并且與 .NET Framework 3.0 或更高版本一起運行時,才會支持 MapRequestHandler、LogRequest 和 PostLogRequest 事件。

  這里要補充一下:從BeginRequest開始的事件,并不是每個事件都會被觸發,因為在整個處理過程中,隨時可以調用Response.End()或者有未處理的異常發生而提前結束整個過程。在那些"知名"的事件中,也只有EndRequest事件是肯定會觸發的,(部分Module的)BeginRequest有可能也不會被觸發。

  對于這些管線事件,我只想提醒2個非常重要的地方:

  1. 每個請求都將會映射到一個HttpHandler,通常也是處理請求的主要對象。

  2. HttpModule可以任意訂閱這些事件,在事件處理器中也可以參與修改請求的操作。

  這2點也決定了HttpHandler和HttpModule的工作方式。

  我找了二張【老圖片】,希望能更直觀的說明ASP.NET管線的處理過程。結合我前面講述的內容,再品味一下老圖片吧。

HttpHandler

  HttpHandler通常是處理請求的核心對象。絕大多數的的請求都在【第10步】被映射到一個HttpHandler,然后在【第15步】中執行處理過程,因此也常把這類對象稱為處理器或者處理程序。我們熟知的Page就是一個處理器,一個ashx文件也是一個處理器,不過ashx顯示得更原始,我們還是來看一下ashx通常是個什么樣子:

  1.   <%@ WebHandler Language="C#" Class="Login" %> 
  2.   using System;  
  3.   using System.Web;  
  4.   public class Login : IHttpHandler {  
  5.   public void ProcessRequest (HttpContext context) {  
  6.   context.Response.ContentType = "text/plain";  
  7.   string username = context.Request.Form["name"];  
  8.   string password = context.Request.Form["password"];  
  9.  if( password == "aaaa" ) {  
  10.   System.Web.Security.FormsAuthentication.SetAuthCookie(username, false);  
  11.   context.Response.Write("OK");  
  12.   }  
  13.   else {  
  14.   context.Response.Write("用戶名或密碼不正確。");  
  15.   }  
  16. }  
  17.   public bool IsReusable {  
  18.   get {  
  19.   return false;  
  20.   }  
  21.   }  
  22.   } 

  可以看到它僅僅是實現一個IHttpHandler接口而已,IHttpHandler接口也很簡單:

  1.   // 定義 ASP.NET 為使用自定義 HTTP 處理程序同步處理 HTTP Web 請求而實現的協定。  
  2.   public interface IHttpHandler  
  3.   {  
  4.   // 獲取一個值,該值指示其他請求是否可以使用 System.Web.IHttpHandler 實例。  
  5.   //  
  6.   // 返回結果:  
  7.   // 如果 System.Web.IHttpHandler 實例可再次使用,則為 true;否則為 false。  
  8.   bool IsReusable { get;}  
  9.   // 通過實現 System.Web.IHttpHandler 接口的自定義 HttpHandler 啟用 HTTP Web 請求的處理。  
  10.   void ProcessRequest(HttpContext context);  
  11.   } 

IsReusable屬性上面有注釋,我就不說了。接口中最重要的部分就是方法 void ProcessRequest(HttpContext context);這個方法簡單地不能再簡單,只有一個參數,但這個參數的能量可不小,有了它幾乎就有了一切,這就是我對它的評價。關于HttpContext的更多詳細介紹請參考我的博客【我心目中的ASP.NET核心對象】。

  在Login.ashx中,我做了三簡單的事:

  1. 讀取輸入數據: 從Request.Form中。

  2. 執行特定的業務邏輯: 一個簡單的判斷。

  3. 返回結果給客戶端: 調用Response.Write()

  是的,就是這三個簡單的操作,但也是絕大多數ashx文件的常規寫法,它的確可以完成一次請求的處理過程。

  記?。?/strong>事實上任何HttpHandler都是這樣處理請求的,只是有時會借助一些框架的包裝而變了味道而已。

  我認為:HttpHandler的強大離不開HttpContext,HttpHandler的重要性是因為管線會將每個請求都映射到一個HttpHandler。

  通常,我們需要新的HttpHandler,創建一個ashx文件就可以了。但也可以創建自己的HttpHandler,或者要將一類【特殊的路徑/擴展名】交給某個處理器來處理,那么就需要我們在web.config中注冊那個處理器。

  注意:如果是【特殊的擴展名】可能還需要在IIS中注冊,原因很簡單:IIS不將請求交給ASP.NET,我們的代碼根本沒機會運行!

  我們可以采用以下方式在web.config中注冊一個自定義的處理器:

  1.   <httpHandlers>  
  2.   <add path="/MyService.axd" verb="*" validate="false" type="MySimpleServiceFramework.MyServiceHandler"/>  
  3.   </httpHandlers> 

  或者:(為了排版,我將一些代碼做了換行處理)

  1.   <httpHandlers>  
  2.   <remove verb="*" path="*.cs"/>  
  3.   <add verb="*" path="*.cs" validate="false" type="FishWebLib.Ajax.AjaxMethodV2Handler,  
  4. FishWebLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=04db02423b9ebbb2"/>  
  5. FishWebLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=04db02423b9ebbb2"/>  
  6.   <remove verb="*" path="*.ascx"/>  
  7.   <add verb="*" path="*.ascx" validate="false" type="FishWebLib.Ajax.UserControlHandler,  
  8. FishWebLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=04db02423b9ebbb2"/>  
  9. FishWebLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=04db02423b9ebbb2"/>  
  10.   </httpHandlers> 

  HttpModule

  前面我已經提到過HttpModule的工作方式:訂閱管線事件,并在事件處理器中執行所需的相關操作。

  這個描述看起來很平淡,但是,它的工作方式給了它無限強大的處理能力。

  它的無限強大的處理能力來源于可以訂閱管線事件,因此,它有能力可以在許多階段修改請求,這些修改最終可能會影響請求的處理。

  前面我說過:“HttpHandler是處理請求的主要對象”,但HttpModule卻可以隨意指定將某個請求交給某個處理器來執行!

  甚至,HttpModule也可以直接處理請求,完全不給HttpHandler工作的機會!

  我們來看一下HttpModule的實現方式:

  1.   /// <summary>  
  2.   /// 能支持雙向GZIP壓縮的Module,它會根據客戶端是否啟用GZIP來自動處理。  
  3.   /// 對于服務來說,不用關心GZIP處理,服務只要處理輸入輸出就可以了。  
  4.   /// </summary>  
  5.   internal class DuplexGzipModule : IHttpModule  
  6.   {  
  7.   public void Init(HttpApplication app)  
  8.   {  
  9.   app.BeginRequest += new EventHandler(app_BeginRequest);  
  10.   }  
  11.   void app_BeginRequest(object sender, EventArgs e)  
  12.   {  
  13.   HttpApplication app = (HttpApplication)sender;  
  14.   // 注意:這里不能使用"Accept-Encoding"這個頭,二者的意義完全不同。  
  15.   if( app.Request.Headers["Content-Encoding"] == "gzip" ) {  
  16.   app.Request.Filter = new GZipStream(app.Request.Filter, CompressionMode.Decompress);  
  17.   app.Response.Filter = new GZipStream(app.Response.Filter, CompressionMode.Compress);  
  18.   app.Response.AppendHeader("Content-Encoding""gzip");  
  19.   }  
  20.   }  
  21.   public void Dispose()  
  22.   {  
  23.   }  
  24.   } 

  每個HttpModule只需要實現IHttpModule接口就可以了。IHttpModule也是個簡單的接口:

  1.   public interface IHttpModule  
  2.   {  
  3.   void Dispose();  
  4.   void Init(HttpApplication app);  
  5.   } 

  在這二個方法中,***個方法通??梢员3譃榭铡W钪匾姆椒ň褪荌nit,它給了HttpModule能訂閱管線事件的機會,然后在相應的事件處理中,我們就可以執行它的具體操作了。

  還記得我在博客【我心目中的ASP.NET核心對象】***給出一個示例嗎?在QueryOrderService.ashx中,為了支持gzip,需要直接調用GZipStream類,對于一二個ashx來說,或許不是問題,如果這樣的處理器變多了,每個處理器都那樣寫,您能受得了嗎?反正我是受不了的,因此今天我把它改成使用Module來實現,代碼簡單了許多。在本文末尾可以下載。

  對于ASP.NET項目來說:當您發現有很多處理輸入輸出的操作非常類似時,那正是HttpModule可以發揮的舞臺,請把這些重復的操作交給它吧。

  讓HttpModule工作也需要在web.config中注冊:

  1.   <httpModules>  
  2.   <add name="DuplexGzipModule" type="MySimpleServiceFramework.DuplexGzipModule"/>  
  3.   </httpModules> 

  通常,我會把一些HttpModule放在類庫中實現,然后在需要使用的項目的web.config中注冊。

  這也體現它的高重用性:寫一次,許多項目就可以直接使用。

  HttpModule的加載方式:前面我說過“ASP.NET會為每個請求分配一個HttpApplication對象”,在每個HttpApplication對象的初始化操作中,它會加載所有在web.config中注冊的HttpModule。由于ASP.NET并不是只創建一個HttpApplication對象,而是多個HttpApplication對象,因此每個HttpModule的Init事件是有可能被多次調用的。許多人喜歡在這里做各類初始化的操作,那么請注意在這里修改靜態變量成員時的線程安全問題。特別地,如果要執行程序初始化的操作,那么還是把代碼放在Global.asax的Application_Start中去處理吧, HttpModule的Init事件并不合適。

  為HttpModule選擇訂閱合適的管線事件:這是非常重要的,訂閱不同的事件,產生的結果也會不一樣。原因也很簡單,在ASP.NET運行環境中,并不只有一個HttpModule,某個HttpModule的判斷可能要依據其它HttpModule的輸出結果,而且在某些(晚期的)管線事件中,也不能再修改輸出數據。在后面的示例中,DirectProcessRequestMoudle訂閱了PostAuthorizeRequest事件,如果訂閱BeginRequest事件或許將得到更好的性能,但是,在BeginRequest事件中,身份認證模塊還沒有工作,因此每個請求在這個事件階段都屬于“未登錄”狀態。

  前面說了一大堆的HttpModule,事實上,在這個示例中,主角是另一個對象:Filter 。上篇博客我就提過它,***為了演示它,把它放在一個HttpHandler里【糟蹋了】,沒辦法,上篇的主題不是管線呀。今天只好和HttpModule一起出場了。我認為Filter還是應該和HttpModule一起使用才能發揮它的獨特價值。Filter的特點還真不合適在HttpHandler中使用,如果您在HttpHandler里使用Filter,我認為有必要考慮一下是不是用錯了。

  借HttpModule的地盤我們來談談Filter。Filter很低調,低調到什么程度:可能很少有人關注過它,因此也少有人用過它。事實也確實如此,一般情況下可以不用它,但用到它,你會發現它非常強大。前面我經常說到【輸入輸出流】,請求的數據,除了請求頭以外,基本上全放在流中,如果您希望對這些數據以流的方式進行處理,特別是希望對于所有請求,或者某類請求,那么使用Filter是非常恰當的。前面的示例就是一個非常合理地使用,好好地品味它,或許您還能發現Filter能做更多的事情。

  選 HttpHandler 還是 HttpModule ?

  HttpHandler是每個請求的主要處理對象,而HttpModule可以選擇請求交給哪個HttpHandler來處理,甚至,它還可以選擇它自己來處理請求。

  下面我給個示例代碼來說明HttpModule也能直接請求:

  1.   /// <summary>  
  2.   /// 此Module示范了直接使用Module也能處理客戶端的請求。  
  3.   /// 建議:除非要很好的理由,否則不建議使用這種方法。  
  4.   /// </summary>  
  5.   internal class DirectProcessRequestMoudle : IHttpModule  
  6.   {  
  7.   public void Init(HttpApplication app)  
  8.   {  
  9.   app.PostAuthorizeRequest += new EventHandler(app_PostAuthorizeRequest);  
  10.   }  
  11.   void app_PostAuthorizeRequest(object sender, EventArgs e)  
  12.   {  
  13.   HttpApplication app = (HttpApplication)sender;  
  14.  ServiceInfo info = GetServiceInfo(app.Context);  
  15.   if( info == null )  
  16.   return;  
  17.   ServiceExecutor.ProcessRequest(app.Context, info);  
  18.   app.Response.End();  
  19.   } 

  為了更好的回答本節的這個問題,我再給段等效的代碼,不過,請求是經過HttpHandler來處理

  1.  internal class MyServiceHandler : IHttpHandler  
  2.   {  
  3.  internal ServiceInfo ServiceInfo { get;set;}  
  4.   public void ProcessRequest(HttpContext context)  
  5.   {  
  6.   ServiceInfo info = this.ServiceInfo ?? GetServiceInfo(context);  
  7.   ServiceExecutor.ProcessRequest(context, info);  
  8.   } 

  HttpHandler和HttpModule都能處理請求,我該選哪個??

  對于此類情況,我的答案是:視情況而定,正如我在注釋中描述的那樣,除非要很好的理由,否則不建議使用HttpModule處理請求。用HttpModule在某些時候可能會快點,關鍵點在于處理完成時要調用Response.End();這會讓后面的事件全都短路,其它的HttpModule就沒有機會執行。如果您的框架或者項目設計很依賴于管線中的事件處理,那么調用Response.End();無疑會破壞這個規則,也將會導致不能得到正確的結果。選擇HttpHandler就不會有這種事情發生。

  不過,也沒有絕對的事情:在請求處理期間,您可以在任何地方調用Response.End(); 結果也是一樣的。

  幸好,短路的情況并不經常發生,因此選擇HttpHandler會讓整個ASP.NET的管線都能發揮作用,因此,我建議優先選擇HttpHandler。

  尤其是在HttpHandler能很好的完成工作的前提下,就應該選HttpHandler,因為選HttpModule會給其它請求帶來不必要的性能損失,具體細節請繼續閱讀。

  其實,我們還可以從另一個角度來看這個問題。

  首先,請仔細地閱讀前面的示例代碼,您是否發現它們在實現方式上非常類似?

  現在應該找到答案了吧:把具體的處理操作分離到HttpHandler,HttpModule之外的地方。那么,此時這個問題也就不是問題了,您也可以提供多種方案供使用者選擇。比如:我就為【我的服務框架】提供了5種方式讓使用者可以輕松地將一個C#方法公開為一個服務方法,該如何選擇這個問題,由使用者來決定,這個問題不會讓我為難。

  我的觀點:在沒有太多技術難度的前提下,提供多種解決辦法應該是對的,您將會避開很多麻煩事情。

  看不見的性能問題

  前面我介紹了HttpModule的重要優點:高重用性。只要寫好一個HttpModule可以放在任何ASP.NET項目中使用,非常方便。

  不過,再好的東西也不能濫用。HttpModule也可以對性能產生負面影響。原因也很簡單:對于每個ASP.NET請求,每個HttpModule都會在它們所訂閱的事件中,去執行一些操作邏輯。這些操作邏輯或許對一些請求是無意義的,但仍會執行。因此,計算機將會白白浪費一些資源去執行一些無意義的代碼。

  知道了原因,解決辦法也就很清楚了:

  1. 去掉不需要的HttpModule

  2. 在每個HttpModule的事件處理器中,首先要確定是不是自己所需要處理的請求。對一些不相關的請求,應該立即退出。

  在我們創建一個ASP.NET項目時,如果不做任何修改,微軟已經為我們加載了好多HttpModule 。請看以下代碼:

  1.   protected void Page_Load(object sender, EventArgs e)  
  2.   {  
  3.   HttpApplication app = HttpContext.Current.ApplicationInstance;  
  4.   StringBuilder sb = new StringBuilder();  
  5.   foreachstring module in app.Modules.AllKeys )  
  6.   sb.AppendFormat(module).Append("<br />");  
  7.   Response.Write(sb.ToString());  
  8.  } 

  輸出結果如下:

總共有14個。

  哎,大多數是我肯定不會用到的,但它們卻被加載了,因此,在它們所訂閱的事件中,它們的代碼將會檢查所有的請求,那些無意義的代碼將有機會執行。如果您不想視而不見,那么請在web.config中做類似的修改,將不需要的Module移除。

  1.   <httpModules> 
  2.   <remove name="Session"/> 
  3.   <remove name="RoleManager"/> 
  4.   </httpModules> 

  HttpModule的第2個需要注意的地方是:HttpModule對所有的請求有效,如果HttpModule不能處理所有的請求,那么請先判斷當前請求是否需要處理,對于不需要處理的請求,應該立即退出。請看以下示例代碼:

  1.   /// <summary>  
  2.   /// 【演示用】讓Aspx頁的請求支持gzip壓縮輸出  
  3.   /// </summary>  
  4.   public class FishGzipModule : IHttpModule  
  5.   {  
  6.   public void Init(HttpApplication app)  
  7.   {  
  8.   app.BeginRequest += new EventHandler(app_BeginRequest);  
  9.  }  
  10.   void app_BeginRequest(object sender, EventArgs e)  
  11.   {  
  12.   HttpApplication app = (HttpApplication)sender;  
  13.   // 這里做個簡單的演示,只處理aspx頁面的輸出壓縮。  
  14.   // 當然了,IIS也提供壓縮功能,這里也僅當演示用,或許可適用于一些特殊場合。  
  15.   if( app.Request.AppRelativeCurrentExecutionFilePath.EndsWith(  
  16.   "aspx", StringComparison.OrdinalIgnoreCase) == false )  
  17.   // 注意:先判斷是不是要處理的請求,如果不是,直接退出。  
  18.   // 而不是:先執行了后面的判斷,再發現不是aspx時才退出。  
  19.   return;  
  20.   string flag = app.Request.Headers["Accept-Encoding"];  
  21.   ifstring.IsNullOrEmpty(flag) == false &&flag.ToLower().IndexOf("gzip") >= 0 ) {  
  22.   app.Response.Filter = new GZipStream(app.Response.Filter, CompressionMode.Compress);  
  23.   app.Response.AppendHeader("Content-Encoding""gzip");  
  24.   }  
  25.   } 

  更多實戰介紹

  本文從這里起,將不再過多的敘述一些理論文字,而是將以實戰的形式展示ASP.NET的強大管線功能,這些實戰展示了一些很經典的應用場景,其中大部分示例代碼將做為【我的服務框架】的關鍵部分。因此請注意理解這些代碼。

  實戰代碼大量使用了上篇博客【我心目中的ASP.NET核心對象】所介紹的絕大多數對象,也算是再次展示那些核心對象的重要性,因此請務必先了解那些核心對象。

  上篇博客僅展示了那些強大對象的功能,單獨使用它們,也是不現實的,今天,我將演示它們與HttpHandler, HttpModule一起并肩工作所能完成的各種任務。

  故事未講完,傳奇在繼續。更多精彩即將上演!

  實戰演示 - 模擬更多的HttpMethod

  近幾年又有一種被稱為RESTful Web服務的概念進入開發人員的視野,它提倡使用HTTP協議提供的GET、POST、PUT和DELETE方法來操作網絡資源。不過,目前的瀏覽器只支持GET、POST這二種方法,因此就有人想到采用HTTP頭,表單值,或者查詢字符串的形式來模擬這些瀏覽器不支持的HTTP方法。每種支持RESTful Web服務的框架都有它們自己的實現方式,今天我將使用HttpModule也來模擬這個操作。最終的結果是可以直接訪問HttpRequest.HttpMethod獲取這些操作的方法名字。

  實現原理:訂閱管線中的BeginRequest事件,檢查當前請求是否需要修改HttpMethod,如果是,則修改HttpMethod屬性。

  所以選擇BeginRequest這個事件,是因為這個事件比較早,可以讓請求的后續階段都能讀到新的結果。

  1.   /// <summary>  
  2.   /// 【演示用】實現了模擬更多 HttpMethod 的Module  
  3.   /// </summary>  
  4.   internal class XHttpMethodModule : IHttpModule  
  5.   {  
  6.   private FieldInfo _field;  
  7.  public void Init(HttpApplication context)  
  8.   {  
  9.   // 訂閱這個較早的事件,可以讓請求的后續階段都能讀到新的結果。  
  10.   context.BeginRequest += new EventHandler(context_BeginRequest);  
  11.   _field = typeof(HttpRequest).GetField("_httpMethod", BindingFlags.Instance | BindingFlags.NonPublic);  
  12.   }  
  13.   void context_BeginRequest(object sender, EventArgs e)  
  14.   {  
  15.   HttpApplication app = (HttpApplication)sender;  
  16.   // 這里僅檢查是否為POST操作,如果您的應用中需要使用GET來模擬的,請修改這里。  
  17.   ifstring.Equals(app.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase) ) {  
  18.   // 這里為了簡單,我只檢查請求頭,如果還需要檢查表單值或者查詢字符串,請修改這里。  
  19.   string headerOverrideValue = app.Request.Headers["X-HTTP-Method-Override"];  
  20.   ifstring.IsNullOrEmpty(headerOverrideValue) == false ) {  
  21.   ifstring.Equals(headerOverrideValue, "GET", StringComparison.OrdinalIgnoreCase) == false &&  
  22.   string.Equals(headerOverrideValue, "POST", StringComparison.OrdinalIgnoreCase) == false ) {  
  23.   // HttpRequest.HttpMethod屬性其實就是訪問_httpMethod這個私有字段,我將直接修改它。  
  24.   // 這樣修改后,最原始的HTTP方法就丟失,通常這或許也是可以接受的。  
  25.   _field.SetValue(app.Request, headerOverrideValue.ToUpper());  
  26.   }  
  27.   }  
  28.   }  
  29.   } 

  我認為采用HttpModule來處理這個問題是個不錯的選擇。它至少有2個好處:

  1. 這個HttpModule能繼續給其它的網站項目使用,因此提高了代碼的重用性。

  2. 我可以隨時決定要不要支持模擬,不需要模擬時,從web.config中不加載它就可以了,因此切換很靈活,且不需要修改現有代碼。

  來看一下頁面及調用結果吧

  1.   protected void Page_Load(object sender, EventArgs e)  
  2.   {  
  3.   Response.Write(Request.HttpMethod);  
  4.   } 

調用結果如下:

實戰演示 - URL重寫

  使用HttpModule來實現URL重寫。這個功能應該是HttpModule非常經典的應用了。

  通常情況下,這種應用常用的方式是將一個URL: /product/12 重寫為 /product.aspx?id=12 ,此時product.aspx應該是一個已經已存在的頁面。顯然重寫后的地址更友好。URL重寫的目的就是能讓URL更友好。

  實現原理:訂閱管線的PostAuthorizeRequest事件,檢查URL是不是期望修改的模式,如果是,則調用Context.RewritePath()完成URL的重寫操作。在管線的后續處理中,最終會使用新的URL來映射到一個合適的HttpHandler。說明:選擇的事件只要在【第10個事件】之前就可以了,因為在第10個事件前重寫URL,才能保證到將請求映射到合適的處理器來執行。就這么簡單,請參考以下代碼:

  1.   public class MyServiceUrlRewriteModule : IHttpModule  
  2.   {  
  3.   // 為了演示簡單,直接寫死地址。  
  4.  // 注意:MyService.axd 必須在web.config中注冊,以保證它能成功映射。  
  5.   public static string RewriteUrlPattern = "/MyService.axd?sc={1}&op={1}";  
  6.   public void Init(HttpApplication app)  
  7.   {  
  8.   app.PostAuthorizeRequest += new EventHandler(app_PostAuthorizeRequest);  
  9.   }  
  10.   void app_PostAuthorizeRequest(object sender, EventArgs e)  
  11.   {  
  12.   HttpApplication app = (HttpApplication)sender;  
  13.   // 這里將檢查URL是否為需要重寫的模式,比如:  
  14.   // http://localhost:11647/service/OrderService/QueryOrder  
  15.   NamesPair pair = FrameworkRules.ParseNamesPair(app.Request);  
  16.   if( pair == null )  
  17.   return;  
  18.   // 開始重寫URL,***將會映射到MyServiceHandler  
  19.   int p = app.Request.Path.IndexOf('?');  
  20.   if( p >0 )  
  21.   app.Context.RewritePath(string.Format(RewriteUrlPattern, pair.ServiceName, pair.MethodName)  
  22.   + "&" + app.Request.Path.Substring(p + 1)  
  23.   );  
  24.   else 
  25.   app.Context.RewritePath(string.Format(RewriteUrlPattern, pair.ServiceName, pair.MethodName));  
  26.   } 

  重寫發生了什么?

  對于一個傳入請求:http://localhost:11647/service/FormDemoService/ShowUrlInfo

  它將被重寫為:http://localhost:11647/MyService.axd?sc=FormDemoService&op=ShowUrlInfo

  由于在web.config中,對MyService.axd已做過注冊,因此ASP.NET會將請求轉交給注冊的處理器來處理它。

  注意:URL重寫,會影響某些變量的值。請參考以下代碼,我將寫個服務方法來檢測這個現象:

  [MyServiceMethod]

  public string ShowUrlInfo(int a)

  {

  System.Web.HttpRequest request = System.Web.HttpContext.Current.Request;

  System.Text.StringBuilder sb = new System.Text.StringBuilder();

  sb.AppendFormat("Path: {0} ", request.Path);

  sb.AppendFormat("RawUrl: {0} ", request.RawUrl);

  sb.AppendFormat("Url.PathAndQuery: {0} ", request.Url.PathAndQuery);

  return sb.ToString();

  }

  輸出結果:

實戰演示 - URL路由

  使用HttpModule來實現URL路由。這個功能隨著ASP.NET MVC框架的出現也逐漸流行起來了。

  URL路由的目標也是為了使用URL更友好,與URL重寫類似。

  實現原理:訂閱管線的PostResolveRequestCache事件,檢查URL是不是期望的路由模式,如果是,則要根據請求中所包含的信息找到一個合適的處理器,并臨時保存這個處理器,重寫URL到一個ASP.NET能映射處理器的地址。在管線的PostMapRequestHandler中,檢查前面有沒有臨時保存的處理器,如果有,則重新給Context.Handler賦值,并重寫URL到原始地址。在管線的后續處理中,最終會使用Context.Handler的HttpHandler。就這么簡單,請參考以下代碼:

  public class MyServiceUrlRoutingModule : IHttpModule

  {

  private static readonly object s_dataKey = new object();

  public void Init(HttpApplication app)

  {

  app.PostResolveRequestCache += new EventHandler(app_PostResolveRequestCache);

  app.PostMapRequestHandler += new EventHandler(app_PostMapRequestHandler);

  }

  private void app_PostResolveRequestCache(object sender, EventArgs e)

  {

  HttpApplication app = (HttpApplication)sender;

  // 獲取合適的處理器,注意這是與URL重寫的根本差別。

  // 即:根據當前請求【主動】尋找一個處理器,而不是使用RewritePath讓ASP.NET替我們去找。

  MyServiceHandler handler = GetHandler(app.Context);

  if( handler == null )

  return;

  // 臨時保存前面獲取到的處理器,這個值將在PostMapRequestHandler事件中再取出來。

  app.Context.Items[s_dataKey] = handler;

  // 進入正常的MapRequestHandler事件,隨便映射到一個處理器就行了。

  app.Context.RewritePath("~/MyServiceUrlRoutingModule.axd");

  }

  private void app_PostMapRequestHandler(object sender, EventArgs e)

  {

  HttpApplication app = (HttpApplication)sender;

  // 取出在PostResolveRequestCache事件中獲得的處理器

  MyServiceHandler handler = (MyServiceHandler)app.Context.Items[s_dataKey];

  if( handler != null ) {

  // 還原URL請求地址。注意這里和URL重寫的差別。

  app.Context.RewritePath(app.Request.RawUrl);

  // 還原根據GetHandler(app.Context)調用得到的處理器。

  // 因為此時app.Context.Handler是由"~/MyServiceUrlRoutingModule.axd"映射得到的。

  app.Context.Handler = handler;

  }

  }

  注意:在MyServiceUrlRoutingModule中,我將請求【路由】到一個MyServiceHandler的實例,而不是讓ASP.NET根據URL來替我選擇。

  在URL重寫的演示中,有些URL相關的屬性發生了改變,我們再來看一下URL路由是個什么結果:

實現自己的服務框架

  本篇博客在開頭說過:將在本次博客中改進上次的服務實現,讓它成為一個真正能用的服務框架。

  前面在講述ASP.NET管線時,給出了很多示例代碼,這些示例代碼都可以在博客的結尾處下載到。這些代碼來源于【我的服務框架】中的部分源代碼,下面我將重點介紹【我的服務框架】。

  利用【我的服務框架】將類公開成服務

  在【我的服務框架】中,一個類要想公開為服務類,并不需要繼承某個類或者實現什么接口,只需要在類上加一個特性就好了,方法也只需加一個特性,示例代碼如下:

  [MyService]

  public class OrderService

  {

  [MyServiceMethod]

  public static string Hello(string name)

  {

  return "Hello " + name;

  }

  [MyServiceMethod]

  public List<Order>QueryOrder(QueryOrderCondition query)

  {

  // 模擬查詢過程,這里就直接返回一個列表。

  List<Order>list = new List<Order>();

  for( int i = 0;i <10;i++ )

  list.Add(DataFactory.CreateRandomOrder());

  return list;

  }

  public string HiddenMethod(string aa)

  {

  // 這個方法應該是不能以服務方式被調用到的。

  throw new NotImplementedException();

  }

  }

  如果某個方法需要只公開給登錄用戶或者指定的用戶,還可以使用以下方式:

  // 這是一個訪問受限的服務類,只允許某些用戶調用。

  [Authorize]

  [MyService]

  public static class LimitService

  {

  [Authorize(Users="fish-li, cc")]

  [MyServiceMethod]

  public static string CalcPassword(string pwd)

  {

  // 這個方法只能由 fish-li, cc 二個用戶來調用

  if( pwd == null )

  pwd = string.Empty;

  byte[] buffer = (new MD5CryptoServiceProvider()).ComputeHash(Encoding.Default.GetBytes(pwd));

  return BitConverter.ToString(buffer).Replace("-", "");

  }

  [MyServiceMethod]

  public static string CalcBase64(string str)

  {

  // 這個方法只能由已登錄用戶調用。

  if( string.IsNullOrEmpty(str) )

  return string.Empty;

  return Convert.ToBase64String(Encoding.UTF8.GetBytes(str));

  }

  }

  就這么簡單,一個類,就可以成為一個服務。

  說明:本框架并不要求將服務類在網站項目中實現,完全可以放在類庫中實現。

  還可以支持Session哦。

  [MyService(SessionMode=SessionMode.Support)]

  public class SessionDemoService

  {

  [MyServiceMethod]

  public int Add(int a)

  {

  // 一個累加的方法,檢驗是否可以訪問Session

  if( System.Web.HttpContext.Current.Session == null )

  throw new InvalidOperationException("Session沒有開啟。");

  object obj = System.Web.HttpContext.Current.Session["counter"];

  int counter = (obj == null ? 0 : (int)obj);

  counter += a;

  System.Web.HttpContext.Current.Session["counter"] = counter;

  return counter;

  }

  }

  SessionMode的定義如下:

  public enum SessionMode

  {

  NotSupport,

  Support,

  ReadOnly

  }

  【我的服務框架】支持的序列化的種類

  在上篇博客中,我演示了使用JSON序列化的做法來實現一個服務響應。本來也是打算讓框架僅支持JSON序列化的,因為傳輸的數據量小嘛。沒想到,做到后來,還是認為有必要把XML序列化也加進來,XML序列化快呀。***,居然想到既然是服務框架,Ajax調用也能算是服務吧,總不能不支持吧,后來干脆也能支持部分的Ajax調用了。

  在【我的服務框架】中,服務端判斷客戶端發送的數據序列化方式是通過判斷請求頭"Serializer-Format"來實現的。序列化的種類還允許繼續自定義。只要實現以下接口:

  public interface ISerializerProvider

  {

  object Deserialize(Type destType, HttpRequest request);

  void Serializer(object obj, HttpResponse response);

  }

  然后調用以下方法就可以了:

  public static class SerializerProviderFactory

  {

  public static void RegisterSerializerProvider(string name, Type type)

  {

  // ...................................

  }

  判斷客戶端的序列化方式,由屬性FrameworkRules.GetSerializerFormat來決定:

  public static class FrameworkRules

  {

  private static string Internal_GetSerializerFormat(HttpRequest request){

  string flag = request.Headers["Serializer-Format"];

  return (string.IsNullOrEmpty(flag) ? "form" : flag);

  }

  private static Func<HttpRequest, string>_serializerFormatRule = Internal_GetSerializerFormat;

  /// <summary>

  /// 此委托用來判斷客戶端發起的請求中,數據是以什么方式序列化的。

  /// 返回的結果將會交給SerializerProviderFactory.GetSerializerProvider()來獲取序列化提供者

  /// 默認的實現是檢查請求頭:"Serializer-Format"

  /// </summary>

  public static Func<HttpRequest, string>GetSerializerFormat

  {

  internal get { return _serializerFormatRule; }

  set

  {

  if( value == null )

  throw new ArgumentNullException("value");

  _serializerFormatRule = value;

  }

  }

  只是一個委托,可以自己重新實現。

  目前本框架提供了三個實現了接口ISerializerProvider的類供用戶使用:JsonSerializerProvider, XmlSerializerProvider, FormSerializerProvider

  這里只展示JsonSerializerProvider的實現:

  internal class JsonSerializerProvider : ISerializerProvider

  {

  private static readonly MethodInfo s_JSSDeserializeMI

  = typeof(JavaScriptSerializer).GetMethod("Deserialize");

  JavaScriptSerializer jss = new JavaScriptSerializer();

  public object Deserialize(Type destType, HttpRequest request)

  {

  StreamReader sr = new StreamReader(request.InputStream, request.ContentEncoding);

  string input = sr.ReadToEnd();

  MethodInfo deserialize = s_JSSDeserializeMI.MakeGenericMethod(destType);

  return deserialize.Invoke(jss, new object[] { input });

  }

  public void Serializer(object obj, HttpResponse response)

  {

  if( obj == null )

  return;

  response.ContentType = "application/json";

  response.Write(jss.Serialize(obj));

  }

  注意:FormSerializerProvider的實現不夠完善,因為再搞下去,就和【我的WEB框架】就重復了。有興趣的自己去完善吧。

  這里再給自己的作品打個廣告:

  【ASP.NET MVC 框架,我也來山寨一下】, 【曬曬我的Ajax服務端框架】, 【我的Ajax服務端框架 - (1) JS直接調用C#方法】

  【我的服務框架】對gzip的支持

  對于gzip的支持,我只想說:太簡單了。

  前面不是已給出DuplexGzipModule的實現代碼嘛。是的,就是把它注冊到web.config中就可以了。

  你說簡不簡單? 完全不用寫多余的代碼,要不要gzip支持,也只是個配置問題!

  說到這里,我想起前段時間Artech寫的一篇博客通過WCF擴展實現消息壓縮,正如我在前篇博客的回復中說到的:“本來真沒興趣看的,不過,為了驗證我的猜想,還是去看了一下,果然也沒讓我失望。”。

  在此,有必要公開一下我的想法:絕對沒有半點看不起Artech的意思,只是我對WCF沒有興趣了。理由也簡單:不夠簡單。

  還是接著說,Artech的博客展示了在WCF中壓縮消息的方式,當然我相信Artech對于WCF的理解,他的方案或許應該是最簡單的解決方案,但是和【我的服務框架】對gzip的支持的易用性根本沒法比。

  WCF的粉絲們,當您看到這里,請先別忙著噴我。聽我說完:WCF的確很強大,我的這個不到700行的框架那也是根本不能和它相比的。

  做這個比較僅僅是為了展示ASP.NET是一個強大的平臺,ASP.NET有更高水準的擴展性。

  利用【我的服務框架】發布服務的5種方式

  【我的服務框架】可以提供5種不同的方式,讓您將一個類及方法公開成一個服務,供外界調用。

  方法1:使用DirectProcessRequestMoudle,只需要配置web.config即可。

  <httpModules>

  <add name="DirectProcessRequestMoudle" type="MySimpleServiceFramework.DirectProcessRequestMoudle"/>

  </httpModules>

  客戶端調用URL:http://localhost:11647/service/OrderService/QueryOrder

  說明:URL模式是可以自由定義的,只要給FrameworkRules.ParseNamesPair賦值即可,它的定義如下:

  public static Func<HttpRequest, NamesPair>ParseNamesPair

  默認的實現方式:

  internal static class UrlPatternHelper

  {

  // 為了演示簡單,我只定義一個URL模式。【因為我認為對于服務來說,一個就夠了】

  // 如果希望適用性更廣,可以從配置文件中讀取,并且可支持多組URL模式。

  // URL中加了"/service/"只是為了能更好地區分其它請求,如果您的網站沒有子目錄,刪除它也是可以的。

  private static readonly string UrlPattern = @"/service/(?<name>[^/]+)/(?<method>[^/]+)[/?]?";

  public static NamesPair ParseNamesPair(HttpRequest request)

  {

  if( request == null )

  throw new ArgumentNullException("request");

  MatchCollection matchs = Regex.Matches(request.Path, UrlPattern);

  if( matchs.Count != 1 )

  return null;

  Match m = matchs[0];

  return new NamesPair {

  ServiceName = m.Result("${name}"),

  MethodName = m.Result("${method}")

  };

  }

  客戶端調用URL: http://localhost:11647/service/OrderService/QueryOrder

  方法2:使用MyServiceUrlRoutingModule,只需要配置web.config即可。

  <httpModules>

  <add name="MyServiceUrlRoutingModule" type="MySimpleServiceFramework.MyServiceUrlRoutingModule"/>

  </httpModules>

  客戶端調用URL: http://localhost:11647/service/OrderService/QueryOrder

  說明:只有這種方式才能支持Session

  方法3:使用MyServiceUrlRewriteModule,只需要配置web.config即可。

  <httpHandlers>

  <add path="/MyService.axd" verb="*" validate="false" type="MySimpleServiceFramework.MyServiceHandler"/>

  </httpHandlers>

  <httpModules>

  <add name="MyServiceUrlRewriteModule" type="MySimpleServiceFramework.MyServiceUrlRewriteModule"/>

  </httpModules>

  客戶端調用URL: http://localhost:11647/service/OrderService/QueryOrder

  方法4:使用MyServiceHandler,只需要配置web.config即可。

  <httpHandlers>

  <add path="/MyService.axd" verb="*" validate="false" type="MySimpleServiceFramework.MyServiceHandler"/>

  </httpHandlers>

  客戶端調用URL: http://localhost:11647/MyService.axd?sc=OrderService&op=QueryOrder

  方法5:創建一個ashx,不需要任何配置。

  <%@ WebHandler Language="C#" Class="MyService" %>

  using System;

  using System.Web;

  using MySimpleServiceFramework;

  public class MyService : IHttpHandler {

  public void ProcessRequest (HttpContext context) {

  NamesPair pair = new NamesPair();

  pair.ServiceName = context.Request.QueryString["sc"];

  pair.MethodName = context.Request.QueryString["op"];

  ServiceExecutor.ProcessRequest(context, pair);

  }

  public bool IsReusable {

  get {

  return false;

  }

  }

  }

  客戶端調用URL: http://localhost:11647/MyService.ashx?sc=OrderService&op=QueryOrder

  注意:前三種方法,需要在IIS中做些額外的配置,因為URL中不包含文件擴展名了,IIS不知道把請求交給ASP.NET來處理。

  具體配置見下圖,此處省略78個字。

我對發布服務的5種方式的建議

  雖然,我給出了5種發布方式,但是我還是想說說我個人的想法。

  在這些方法中,使用URL重寫,URL路由的方法,并不是我想推薦的,寫它們是主要是為了展示HttpModule 。不推薦它們是因為它們要判斷URL是否符合指定模式,這個判斷是有成本的。至于成本有多高,特此,我專了做門的測試。在示例代碼壓縮包中有個___TestRoutePerformance目錄,結果如何,還是您自己去看吧,我也有點累了。

  此外,我想問:對于服務來說,URL友好有多大意義?服務的URL會讓用戶來輸入還是讓Google的爬蟲來訪問?

  如果以上二個問題都是否定的,那么,這二種方法就是在白白浪費機器的性能了。

  當然了,如果您的站點訪問量不大,那么這點性能也可以忽略不計了,就當我沒說。

  使用URL重寫URL路由,還有個比較麻煩的事情:如果想通過URL多傳遞一個參數,那么,是不是又要修改URL模式?

  對于使用DirectProcessRequestMoudle這種模式,我以前已經說過了:除非要很好的理由,否則不建議使用這種方法。

  至于其它的二種方式,本質上是一樣的,只是說:處理器誰來寫的差別了。

  不過,如果您要是選擇手工創建一個處理器,除了不用修改web.config之外,還可以自定義URL參數名,可以選擇要不要支持Session

  【我的服務框架】的一些核心類

  ReflectionHelper類用于根據類名及服務名定位到一個服務類型以及要調用的方法。

  因此,它在框架中的作用也是非常關鍵的。

  internal static class ReflectionHelper

  {

  private static List<TypeAndAttrInfo>s_typeList;

  static ReflectionHelper()

  {

  InitServiceTypes();

  }

  /// <summary>

  /// 加載所有的服務類型,判斷方式就是檢查類型是否有MyServiceAttribute

  /// </summary>

  private static void InitServiceTypes()

  {

  s_typeList = new List<TypeAndAttrInfo>(256);

  ICollection assemblies = BuildManager.GetReferencedAssemblies();

  foreach( Assembly assembly in assemblies ) {

  try {

  (from t in assembly.GetExportedTypes()

  let a = (MyServiceAttribute[])t.GetCustomAttributes(typeof(MyServiceAttribute), false)

  where a.Length >0

  select new TypeAndAttrInfo {

  ServiceType = t, Attr = a[0], AuthorizeAttr = t.GetClassAuthorizeAttribute() }

  ).ToList().ForEach(b => s_typeList.Add(b));

  }

  catch { }

  }

  }

  private static AuthorizeAttribute GetClassAuthorizeAttribute(this Type t)

  {

  AuthorizeAttribute[] attrs = (AuthorizeAttribute[])t.GetCustomAttributes(typeof(AuthorizeAttribute), false);

  return (attrs.Length >0 ? attrs[0] : null);

  }

  /// <summary>

  /// 根據一個名稱獲取對應的服務類型(從緩存中獲取類型)

  /// </summary>

  /// <param name="typeName"></param>

  /// <returns></returns>

  private static TypeAndAttrInfo GetServiceType(string typeName)

  {

  if( string.IsNullOrEmpty(typeName) )

  throw new ArgumentNullException("typeName");

  // 查找類型的方式:如果有點號,則按全名來查找(包含命名空間),否則只看名字。

  // 本框架對于多個匹配條件的類型,將返回***個匹配項。

  if( typeName.IndexOf('.') >0 )

  return s_typeList.FirstOrDefault(t => string.Compare(t.ServiceType.FullName, typeName, true) == 0);

  else

  return s_typeList.FirstOrDefault(t => string.Compare(t.ServiceType.Name, typeName, true) == 0);

  }

  private static Hashtable s_methodTable = Hashtable.Synchronized(

  new Hashtable(4096, StringComparer.OrdinalIgnoreCase));

  /// <summary>

  /// 根據指定的類型以及方法名稱,獲取對應的方法信息

  /// </summary>

  /// <param name="type"></param>

  /// <param name="methodName"></param>

  /// <returns></returns>

  private static MethodAndAttrInfo GetServiceMethod(Type type, string methodName)

  {

  if( type == null )

  throw new ArgumentNullException("type");

  if( string.IsNullOrEmpty(methodName))

  throw new ArgumentNullException("methodName");

  // 首先嘗試從緩存中讀取

  string key = methodName + "@" + type.FullName;

  MethodAndAttrInfo mi = (MethodAndAttrInfo)s_methodTable[key];

  if( mi == null ) {

  // 注意:這里不考慮方法的重載。

  MethodInfo method = type.GetMethod(methodName,

  BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);

  if( method == null )

  return null;

  MyServiceMethodAttribute[] attrs = (MyServiceMethodAttribute[])

  method.GetCustomAttributes(typeof(MyServiceMethodAttribute), false);

  if( attrs.Length != 1 )

  return null;

  // 由于服務方法的參數來源于反序列化,此時只可能包含一個參數。

  ParameterInfo[] paraInfos = method.GetParameters();

  if( paraInfos.Length != 1 )

  throw new ArgumentNullException("指定的方法雖找到,但該方法的參數數量不是1");

  AuthorizeAttribute[] auths = (AuthorizeAttribute[])method.GetCustomAttributes(typeof(AuthorizeAttribute), false);

  mi = new MethodAndAttrInfo {

  MethodInfo = method,

  ParamType = paraInfos[0].ParameterType,

  Attr = attrs[0],

  AuthorizeAttr = (auths.Length >0 ? auths[0] : null)

  };

  s_methodTable[key] = mi;

  }

  return mi;

  }

  /// <summary>

  /// 根據類型名稱以及方法名稱返回要調用的相關信息

  /// </summary>

  /// <param name="pair">包含類型名稱以及方法名稱的對象</param>

  /// <returns></returns>

  public static InvokeInfo GetInvokeInfo(NamesPair pair)

  {

  if( pair == null )

  throw new ArgumentNullException("pair");

  InvokeInfo vkInfo = new InvokeInfo();

  vkInfo.ServiceTypeInfo = GetServiceType(pair.ServiceName);

  if( vkInfo.ServiceTypeInfo == null )

  return null;

  vkInfo.MethodAttrInfo = GetServiceMethod(vkInfo.ServiceTypeInfo.ServiceType, pair.MethodName);

  if( vkInfo.MethodAttrInfo == null )

  return null;

  if( vkInfo.MethodAttrInfo.MethodInfo.IsStatic == false )

  vkInfo.ServiceInstance = Activator.CreateInstance(vkInfo.ServiceTypeInfo.ServiceType);

  return vkInfo;

  }

  }

  ServiceExecutor用于調用服務方法,前面所說的5種服務發布方式,最終都要經過這里。

  /// <summary>

  /// 最終調用服務方法的工具類。

  /// </summary>

  public static class ServiceExecutor

  {

  internal static void ProcessRequest(HttpContext context, ServiceInfo info)

  {

  if( context == null )

  throw new ArgumentNullException("context");

  if( info == null || info.InvokeInfo == null )

  throw new ArgumentNullException("info");

  //if( context.Request.InputStream.Length == 0 )

  // throw new InvalidDataException("沒有調用數據,請將調用數據以請求體的方式傳入。");

  if( info.InvokeInfo.AuthenticateRequest(context) == false )

  ExceptionHelper.Throw403Exception(context);

  // 獲取客戶端的數據序列化格式。

  // 默認實現方式:request.Headers["Serializer-Format"];

  // 注意:這是我自定義的請求頭名稱,也可以不指定,默認為:form (表單)

  string serializerFormat = FrameworkRules.GetSerializerFormat(context.Request);

  ISerializerProvider serializerProvider =

  SerializerProviderFactory.GetSerializerProvider(serializerFormat);

  // 獲取要調用方法的參數類型

  Type destType = info.InvokeInfo.MethodAttrInfo.ParamType;

  // 獲取要調用的參數

  context.Request.InputStream.Position = 0;// 防止其它Module讀取過,但沒有歸位。

  object param = serializerProvider.Deserialize(destType, context.Request);

  // 調用服務方法

  object result = info.InvokeInfo.MethodAttrInfo.MethodInfo.Invoke(

  info.InvokeInfo.ServiceInstance, new object[] { param });

  // 寫輸出結果

  if( result != null )

  serializerProvider.Serializer(result, context.Response);

  }

  /// <summary>

  /// 【外部接口】用于根據服務的類名和方法名執行某個請求

  /// </summary>

  /// <param name="context"></param>

  /// <param name="pair"></param>

  public static void ProcessRequest(HttpContext context, NamesPair pair)

  {

  if( pair == null )

  throw new ArgumentNullException("pair");

  if( string.IsNullOrEmpty(pair.ServiceName) || string.IsNullOrEmpty(pair.MethodName) )

  ExceptionHelper.Throw404Exception(context);

  InvokeInfo vkInfo = ReflectionHelper.GetInvokeInfo(pair);

  if( vkInfo == null )

  ExceptionHelper.Throw404Exception(context);

  ServiceInfo info = new ServiceInfo(pair, vkInfo);

  ProcessRequest(context, info);

  }

  }

  關于寫框架

  以前曾在網上見過幾次關于“重復造輪子”的說法?;旧?,全是反對意見。

  我今天也想談談這個話題:要不要自己寫框架?

  我的答案是:要。

  自己寫框架的好處不在于能將它做得多強大,多***,而是從寫框架的過程中,可以學到很多東西。

  一個框架寫完了,不在乎要給多少人使用,而是自己感覺有沒有進步,這才是關鍵。

  如果您不重復造輪子,可能永遠不知道造輪子的過程,造輪子需要什么內容。

  如果不重復造輪子,世界上將只有一個操作系統。

  如果不重復造輪子,世界上將只有一個數據庫。

  如果不重復造輪子,世界上將只有一種編程語言。

  這樣單調會很好嗎?

  WebForms, MVC, Linq To SQL, Entity Framework, Remoting, Web Service, WCF

  微軟不也是一直在重復造輪子嗎?

  結束語

  終于寫完了這篇博客。這篇博客花了我最多的寫作時間:整整二個周末還加上一些業余時間。

  當然了,其中也包括實現框架的時間。這其實也是希望更好地展示ASP.NET的核心內容。

  寫框架不是目標,展示ASP.NET技術,寫博客才是目標哦。

  因此,這篇博客可謂是【下足了料】,只希望:

  1. 能給大家分享ASP.NET的核心內容。

  2. 這篇博客的成績能趕上【細說Cookie】。

  我的寫博熱情也離不開您的肯定支持哦,如果您認為閱讀此文有收獲,別忘了點一下右下角的【推薦】,【關注 Fish Li】。

  ASP.NET是個偉大的平臺,能完成許多應用。這些應用的實現離不開這二次博客所提過的核心對象的支持,

  本來還想再寫點其它的示例,只是時間花得太多了。今天的示例已經很精彩了,到此為止吧,我也想休息了。

  每個對象都是一個不朽的傳奇,每個傳奇背后都有一個精彩的故事。

  我是Fish Li, 感謝大家閱讀我的博客,請繼續關注我的后續博客。

  點擊此處下載示例代碼

責任編輯:彭凡 來源: 博客園
相關推薦

2009-06-01 10:23:31

asp.net mvcasp.net mvc.net mvc框架

2009-07-24 13:20:44

MVC框架ASP.NET

2009-07-27 16:57:51

ASP.NET系列

2009-07-22 10:34:37

ActionInvokASP.NET MVC

2009-07-22 10:09:59

ASP.NET MVC

2009-07-20 10:53:59

ASP.NET MVC

2009-07-22 13:24:24

ASP.NET MVC

2009-07-23 10:52:38

2009-07-22 15:58:52

ASP.NET AJA

2009-07-20 12:59:53

ASP.NET MVCASP.NET框架的功

2009-07-20 17:39:36

WCF服務ASP.NET AJA

2009-07-29 16:41:45

ASP.NET頁面框架

2009-07-22 13:08:55

拯救UpdatePanASP.NET MVC

2009-07-28 16:03:23

ASP.NET狀態服務

2009-07-28 15:53:43

ASP.NET Web

2009-07-27 17:54:39

WCF服務ASP.NET

2009-07-24 10:41:00

asp.net mvc

2009-08-03 14:22:33

什么是ASP.NET

2009-07-28 17:17:19

ASP.NET概述

2009-07-22 17:45:35

ASP.NET教程
點贊
收藏

51CTO技術棧公眾號

最近日韩中文字幕中文| 欧美在线观看视频一区二区三区| 国产精品日韩一区二区三区| 中文字幕视频网站| 久久精品青草| 亚洲国产精品专区久久| 最新中文字幕免费视频| 日韩电影免费观看| 国产亚洲精品久| 亚洲xxxx18| 综合网在线观看| 91精品国产视频| 亚洲精品之草原avav久久| 亚洲一区二区三区观看| www.成人爱| 亚洲蜜桃精久久久久久久| 欧美极品视频一区二区三区| 国产黄色一级大片| 七七婷婷婷婷精品国产| 高清在线视频日韩欧美| 萌白酱视频在线| 免费一区二区| 亚洲激情在线视频| japan高清日本乱xxxxx| 成人影院在线免费观看| 黑人巨大精品欧美一区二区三区 | 欧美日韩精品免费| 精品国产一二三四区| √天堂8在线网| 国产精品久久久久久久久久免费看 | 欧美日韩二三区| 蜜臀av国内免费精品久久久夜夜| 国产精品三级电影| 欧美一区1区三区3区公司| 日韩永久免费视频| 成人免费高清视频在线观看| 91av免费看| a在线观看视频| 国内国产精品久久| 91精品久久久久久久久久久久久 | 欧美美女一区二区在线观看| 国产黄色特级片| 日韩欧美精品一区二区三区| 亚洲第一福利视频在线| 国产毛片久久久久久国产毛片| bt在线麻豆视频| 亚洲欧美国产毛片在线| 欧美日韩在线免费观看视频| 99reav在线| 欧美高清一级片在线观看| 欧美日韩亚洲在线| 欧美偷拍视频| 久久久久国产精品厨房| 欧美日韩亚洲一区二区三区在线观看| 手机av在线免费观看| 99精品久久免费看蜜臀剧情介绍| 国产精品久久国产精品| 日韩在线观看视频一区| 不卡一区二区在线| 久久精品日韩| 青青久草在线| 中文字幕的久久| 伊甸园精品99久久久久久| 欧美尤物美女在线| 亚洲欧美电影一区二区| 国产精品夜夜夜爽张柏芝| 麻豆网在线观看| 国产精品久久久久久久久图文区| 伊人色综合影院| 激情视频在线观看| 亚洲美女视频在线| 国产精品久久久久久久久电影网| 91色在线看| 欧美视频在线观看 亚洲欧| 青青草精品视频在线观看| 福利视频一区| 日韩一区二区精品在线观看| 中文字幕第九页| 欧美激情影院| 社区色欧美激情 | 久热这里有精品| 中文在线一区| 国产综合久久久久久| 韩国av免费在线| 久久久高清一区二区三区| 亚洲午夜激情| av福利在线导航| 欧美色图天堂网| 无码国产精品一区二区高潮| 精品资源在线| 最近2019年中文视频免费在线观看 | 高清一区二区三区四区| 久久久久久久久久久久久久久99| 在线一区亚洲| 电影在线观看一区| 欧美色综合影院| 亚洲欧美综合视频| 青青草91久久久久久久久| 欧美激情奇米色| 久草热在线观看| 国产99久久久久| 日韩国产高清一区| 成人免费一区二区三区牛牛| 欧美三级资源在线| 97人妻精品一区二区三区免费| 粉嫩av一区二区| 在线日韩第一页| 日韩av一区二区在线播放| 日本美女一区二区三区视频| 高清国产在线一区| 午夜激情视频在线| 欧美性猛xxx| 欧洲成人午夜精品无码区久久| 欧美限制电影| 26uuu另类亚洲欧美日本一| 国产女18毛片多18精品| 国产欧美日韩综合| 欧美精品99久久| 超碰在线一区| 九九精品在线视频| 国产免费久久久| 欧美激情综合五月色丁香 | 粉嫩av性色av蜜臀av网站| 亚洲欧美日本国产专区一区| 成人蜜桃视频| 怡红院av在线| 7777精品伊人久久久大香线蕉的 | www色aa色aawww| 日韩国产欧美在线播放| 免费国产一区| 松下纱荣子在线观看| 精品国产网站在线观看| 成人观看免费视频| 精品亚洲免费视频| 香蕉久久夜色| 成人精品动漫| 在线观看成人黄色| 夜夜躁日日躁狠狠久久av| 久久免费午夜影院| www黄色日本| 欧美xxxx在线| 欧美一级视频一区二区| 深夜福利在线视频| 欧美性xxxx| 在线免费观看黄色小视频| 99热这里只有精品8| 国产一级特黄a大片99| 波多野在线观看| 精品国产一区二区在线观看| 免费一级特黄特色大片| 成人污视频在线观看| 91免费黄视频| 丝袜av一区| 欧美中文字幕视频在线观看| 日韩美女一级视频| 色又黄又爽网站www久久| 成人午夜福利一区二区| 爽好久久久欧美精品| 日韩av在线电影观看| 亚洲精品555| 久久精品99久久香蕉国产色戒| 国产视频在线一区| 亚洲国产欧美一区二区三区丁香婷| 日韩女优在线视频| 噜噜噜91成人网| 日韩欧美国产二区| 亚洲日本中文| 91精品成人久久| 国产视频第一区| 欧美另类高清zo欧美| 久久婷婷国产麻豆91| 久久综合国产精品| jizz18女人| 亚洲国产裸拍裸体视频在线观看乱了中文| 好吊妞www.84com只有这里才有精品 | 福利在线午夜| 6080国产精品一区二区| 国产无遮挡又黄又爽又色| 久久影视一区二区| 色91精品久久久久久久久| 亚洲欧美一区在线| 麻豆91蜜桃| 电影一区二区三区久久免费观看| 韩国三级日本三级少妇99| 国产女主播在线写真| 91精品国产综合久久福利软件| 日本五十熟hd丰满| 国产精品免费av| 伊人网综合视频| 麻豆国产精品777777在线| 日韩精品视频在线观看视频| 国产不卡一区| 国产精品一区二区三区不卡| 97久久网站| 91精品国产免费久久久久久| 色网站在线看| 精品一区电影国产| 国产乱码一区二区| 日本韩国欧美一区二区三区| 99热精品免费| 国产精品久久综合| 在线观看国产网站| 国产成人免费xxxxxxxx| 美女黄色片视频| 在线午夜精品| 男人天堂新网址| 久久精品国产99久久| 久久精品成人一区二区三区蜜臀| 国产日韩欧美中文在线| 日本一区二区三区在线播放| 欧美野外wwwxxx| 日韩视频免费中文字幕| 日本福利片在线| 亚洲电影免费观看高清| 国产露脸国语对白在线| 欧美性受xxxx| 无码人妻精品一区二区| 无码av中文一区二区三区桃花岛| 天天鲁一鲁摸一摸爽一爽| 国产欧美一区二区在线| www.超碰97| av在线播放成人| 久久国产劲爆∧v内射| 国产精品综合av一区二区国产馆| 国产精品wwwww| 亚洲私人影院| www.国产在线视频| 欧美视频导航| 伊人再见免费在线观看高清版| 国产精品国产一区| 亚洲一区三区视频在线观看| 欧美午夜精品一区二区三区电影| 欧美日韩国产高清视频| 色先锋久久影院av| 久久99精品久久久久久秒播放器 | 久久九九热免费视频| 四虎久久免费| 色婷婷综合成人av| 思思99re6国产在线播放| 在线一区二区日韩| 日韩黄色影院| 日韩在线观看免费高清| 日韩精品毛片| 久久色免费在线视频| 成人ww免费完整版在线观看| 爱福利视频一区| 麻豆视频在线观看免费| 免费97视频在线精品国自产拍| www.欧美日本韩国| 欧美国产在线电影| 久久大胆人体| 欧美在线免费看| 99久久综合国产精品二区| 国产欧美精品日韩| 国产一区二区三区黄网站| 成人欧美一区二区| 欧美日韩精品一区二区三区在线观看| 国产在线精品日韩| 国产精品一国产精品| 亚洲精品久久久久久一区二区| 久久国产综合| 中文字幕免费高| 亚洲激情偷拍| 久草综合在线观看| 久久精品国产99| wwwww在线观看| 91色porny| 日韩亚洲欧美中文字幕| 亚洲激情校园春色| 久久久久久久久久久久久久av| 一本色道久久综合亚洲91| 中文字幕+乱码+中文乱码91| 欧美一区二区三区在线观看| 免费的黄色av| 亚洲欧美精品在线| 永久免费av片在线观看全网站| 久久久精品在线观看| av成人福利| 国产精品久久久久久久久久| 视频精品一区二区三区| 成人激情在线观看| av在线国产精品| 国产伦精品一区二区三区| 国产成+人+综合+亚洲欧美| 亚洲一区二区三区视频播放| 久久超级碰碰| 精品无码久久久久国产| 成人免费在线观看av| 4444在线观看| 日韩精品一二三四| 天天操精品视频| 久久噜噜亚洲综合| 91麻豆精品成人一区二区| 五月综合激情网| 97国产精品久久久| 日韩h在线观看| 久草中文在线| 国产91精品高潮白浆喷水| 亚洲日韩中文字幕一区| 欧美日韩精品久久| 精品69视频一区二区三区Q| 麻豆一区二区三区视频| 99re这里只有精品视频首页| 性少妇xx生活| 欧美日韩午夜激情| 国产黄频在线观看| 这里只有精品视频在线| 亚洲第一av| 动漫一区二区在线| 国产精品91一区二区三区| 国产精品宾馆在线精品酒店| 国产mv日韩mv欧美| 免费黄色激情视频| 日本丰满少妇一区二区三区| 免费观看的毛片| 美女视频黄免费的亚洲男人天堂| 国产精品高清乱码在线观看| 精品视频一区在线| 红桃视频亚洲| gogo亚洲国模私拍人体| 成人免费一区二区三区视频| 丰满少妇xoxoxo视频| 精品99一区二区| 特级毛片在线| 91久久精品国产| 日韩免费看片| 国产又大又黄又粗的视频| 99久久精品国产麻豆演员表| 久久精品这里有| 欧美一卡二卡三卡四卡| 嫩草香蕉在线91一二三区| 国产精品久久久久久久7电影| 日韩精品丝袜美腿| 少妇人妻无码专区视频| 成人一区二区三区视频| 国产一二三四在线| 日韩精品一区二区三区在线观看 | 欧美做受高潮中文字幕| 一区二区三区欧美久久| 国产偷人妻精品一区二区在线| 日韩中文字幕国产精品| 亚洲毛片在线免费| 一区二区在线不卡| 激情图区综合网| 永久免费未视频| 日韩一级大片在线观看| 欧美78videosex性欧美| 成人欧美一区二区三区黑人免费| 狠狠色丁香久久综合频道| 97精品人人妻人人| 精品国产鲁一鲁一区二区张丽| 香蕉视频成人在线| 欧美一级大胆视频| 一本久久青青| 久久精品影视大全| 亚洲欧洲日韩综合一区二区| 国产成人精品a视频| 国内免费精品永久在线视频| 天天躁日日躁成人字幕aⅴ| 可以免费在线看黄的网站| 中文字幕欧美激情| 国产乱叫456在线| 久久久久女教师免费一区| 亚洲大片精品免费| 精品亚洲一区二区三区四区| 136国产福利精品导航| 亚洲AV无码成人片在线观看| 97久久精品国产| 国产日产精品_国产精品毛片| 午夜精品中文字幕| 亚洲自拍偷拍综合| 亚洲av电影一区| 国产精品一区av| 国产精品www994| 四虎永久免费在线观看| 欧美精品v日韩精品v韩国精品v| 欧美人动性xxxxz0oz| 欧美黑人3p| 国产精品资源在线看| 在线观看日韩中文字幕| 日日骚av一区| 噜噜噜狠狠夜夜躁精品仙踪林| 欧美精品性生活| 亚洲一区二区3| h网站视频在线观看| av一区二区三区在线观看| 久久av在线| 免费网站观看www在线观| 亚洲欧美日韩精品久久| 蜜桃在线一区| 日本熟妇人妻xxxxx| 亚洲在线视频一区| 成人在线高清视频| yellow视频在线观看一区二区| 噜噜噜91成人网| 国产精品9191| 久久精品最新地址| 国产一区国产二区国产三区| 人妻精品久久久久中文字幕69| 在线观看亚洲a|