您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么用ASP.NET寫服務(wù)框架”,在日常操作中,相信很多人在怎么用ASP.NET寫服務(wù)框架問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么用ASP.NET寫服務(wù)框架”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
首先我要談的話題是ASP.NET的請求處理【管線】,我認(rèn)為這是ASP.NET中最重要的內(nèi)容了,所有到達(dá)ASP.NET的請求都要經(jīng)過管線來處理,不管是WebForms, MVC, WebService, WCF(ASP.NET的承載方式),還是其它微軟的采用HTTP協(xié)議的框架。為什么這些框架都選擇要ASP.NET做為它們的運(yùn)行平臺呢?
我們可以考慮一下:如果讓您從無到有設(shè)計(jì)一個(gè)服務(wù)框架,有哪些事件是必須要處理的?
我想有三個(gè)最根本的事件要做:1. 監(jiān)聽請求端口,2. 為每個(gè)傳入的連接請求分配線程來執(zhí)行具體的響應(yīng)操作,3. 要把請求的數(shù)據(jù)讀出來,并負(fù)責(zé)將處理后的響應(yīng)數(shù)據(jù)發(fā)送給調(diào)用者。
這其實(shí)是個(gè)比較復(fù)雜也很枯燥的過程,但每個(gè)服務(wù)器端程序都需要這些基本功能。幸好IIS和ASP.NET可以為我們做好這些事情,所以那些框架選擇ASP.NET平臺就可以省去這些復(fù)雜的任務(wù)。使用ASP.NET平臺不僅可以簡化設(shè)計(jì),它還有著良好的擴(kuò)展性以滿足更多的框架在這個(gè)平臺上面繼續(xù)開發(fā),而這個(gè)良好擴(kuò)展性是離不開它的請求處理管線的。
ASP.NET是一個(gè)功能完善的平臺框架,它既提供一些高層次的框架供我們使用,比如:WebForms, MVC, WebService,也提供一些低層次的機(jī)制讓我們使用,以便于讓我們開發(fā)有特殊要求的新框架,新解決方案。這個(gè)低層次的機(jī)制就是請求處理管線,使用這個(gè)管線的有二類對象:HttpHandler, HttpModule,控制這條管線工作的對象是:HttpApplication 。通常情況下,我們并不需要直接使用HttpApplication對象,因此本文的主題將主要介紹HttpHandler, HttpModule這二類對象的功能以及如何使用它們。
理解ASP.NET管線
管線(Pipeline)這個(gè)詞也是很有點(diǎn)意思,這個(gè)詞也形象地說明了每個(gè)ASP.NET請求的處理過程:請求是在一個(gè)管道中,要經(jīng)過一系列的過程點(diǎn),這些過程點(diǎn)連接起來也就形成一條線。以上是我對于這個(gè)詞的理解,如果有誤,懇請給予指正。這些一系列的過程點(diǎn),其實(shí)就是由HttpApplication引發(fā)的一系列事件,通??梢杂蒆ttpModule來訂閱,也可以在Global.asax中訂閱,這一系列的事件也就構(gòu)成了一次請求的生命周期。
事件模式,也就是觀察者模式。根據(jù)【C# 3.0 設(shè)計(jì)模式】一書中的定義:“觀察者模式定義了對象之間的一種聯(lián)系,使得當(dāng)一個(gè)對象改變狀態(tài)時(shí),所有其它的對象都可以相應(yīng)地被通知到。" ASP.NET的管線設(shè)計(jì)正是采用了這種方式,在這個(gè)設(shè)計(jì)模式中,觀察者就是許多HttpModule對象,被觀察的對象就是每個(gè)”請求“,它的狀態(tài)是由HttpApplication控制,用于描述當(dāng)前請求的處理階段,HttpApplication會根據(jù)一個(gè)特定的順序修改這個(gè)狀態(tài),并在每個(gè)狀態(tài)改變后引發(fā)相應(yīng)的事件。 ASP.NET會為每個(gè)請求分配一個(gè)HttpApplication對象來引發(fā)這些事件,因此可以讓一大批觀察者了解每個(gè)請求的狀態(tài),每個(gè)觀察者也可以在感興趣的時(shí)候修改請求的一些數(shù)據(jù)。這些與請求相關(guān)的數(shù)據(jù)的也就是我上篇博客中提到的HttpRequest, HttpResponse。正是由于引入了事件機(jī)制,ASP.NET框架也有了極強(qiáng)的擴(kuò)展能力。再來看看管線處理請求的過程,我將直接引用MSDN中的原文【IIS 5.0 和 6.0 的 ASP.NET 應(yīng)用程序生命周期概述】中的片段。
在處理該請求時(shí)將由 HttpApplication 類執(zhí)行以下事件。 希望擴(kuò)展 HttpApplication 類的開發(fā)人員尤其需要注意這些事件。
1. 對請求進(jìn)行驗(yàn)證,將檢查瀏覽器發(fā)送的信息,并確定其是否包含潛在惡意標(biāo)記。 有關(guān)更多信息,請參見 ValidateRequest 和腳本侵入概述。
2. 如果已在 Web.config 文件的 UrlMappingsSection 節(jié)中配置了任何 URL,則執(zhí)行 URL 映射。
3. 引發(fā) BeginRequest 事件。
4. 引發(fā) AuthenticateRequest 事件。
5. 引發(fā) PostAuthenticateRequest 事件。
6. 引發(fā) AuthorizeRequest 事件。
7. 引發(fā) PostAuthorizeRequest 事件。
8. 引發(fā) ResolveRequestCache 事件。
9. 引發(fā) PostResolveRequestCache 事件。
10. 根據(jù)所請求資源的文件擴(kuò)展名(在應(yīng)用程序的配置文件中映射),選擇實(shí)現(xiàn) IHttpHandler 的類,對請求進(jìn)行處理。 如果該請求針對從 Page 類派生的對象(頁),并且需要對該頁進(jìn)行編譯,則 ASP.NET 會在創(chuàng)建該頁的實(shí)例之前對其進(jìn)行編譯。
11. 引發(fā) PostMapRequestHandler 事件。
12. 引發(fā) AcquireRequestState 事件。
13. 引發(fā) PostAcquireRequestState 事件。
14. 引發(fā) PreRequestHandlerExecute 事件。
15. 為該請求調(diào)用合適的 IHttpHandler 類的 ProcessRequest 方法(或異步版 IHttpAsyncHandler.BeginProcessRequest)。 例如,如果該請求針對某頁,則當(dāng)前的頁實(shí)例將處理該請求。
16. 引發(fā) PostRequestHandlerExecute 事件。
17. 引發(fā) ReleaseRequestState 事件。
18. 引發(fā) PostReleaseRequestState 事件。
19. 如果定義了 Filter 屬性,則執(zhí)行響應(yīng)篩選。
20. 引發(fā) UpdateRequestCache 事件。
21. 引發(fā) PostUpdateRequestCache 事件。
22. 引發(fā) EndRequest 事件。
23. 引發(fā) PreSendRequestHeaders 事件。
24. 引發(fā) PreSendRequestContent 事件。
如果是IIS7,第10個(gè)事件也就是MapRequestHandler事件,而且在EndRequest 事件前,還增加了另二個(gè)事件:LogRequest 和 PostLogRequest 事件。
只有當(dāng)應(yīng)用程序在 IIS 7.0 集成模式下運(yùn)行,并且與 .NET Framework 3.0 或更高版本一起運(yùn)行時(shí),才會支持 MapRequestHandler、LogRequest 和 PostLogRequest 事件。
這里要補(bǔ)充一下:從BeginRequest開始的事件,并不是每個(gè)事件都會被觸發(fā),因?yàn)樵谡麄€(gè)處理過程中,隨時(shí)可以調(diào)用Response.End()或者有未處理的異常發(fā)生而提前結(jié)束整個(gè)過程。在那些"知名"的事件中,也只有EndRequest事件是肯定會觸發(fā)的,(部分Module的)BeginRequest有可能也不會被觸發(fā)。
對于這些管線事件,我只想提醒2個(gè)非常重要的地方:
1. 每個(gè)請求都將會映射到一個(gè)HttpHandler,通常也是處理請求的主要對象。
2. HttpModule可以任意訂閱這些事件,在事件處理器中也可以參與修改請求的操作。
這2點(diǎn)也決定了HttpHandler和HttpModule的工作方式。
我找了二張【老圖片】,希望能更直觀的說明ASP.NET管線的處理過程。結(jié)合我前面講述的內(nèi)容,再品味一下老圖片吧。
HttpHandler
HttpHandler通常是處理請求的核心對象。絕大多數(shù)的的請求都在【第10步】被映射到一個(gè)HttpHandler,然后在【第15步】中執(zhí)行處理過程,因此也常把這類對象稱為處理器或者處理程序。我們熟知的Page就是一個(gè)處理器,一個(gè)ashx文件也是一個(gè)處理器,不過ashx顯示得更原始,我們還是來看一下ashx通常是個(gè)什么樣子:
<%@ WebHandler Language="C#" Class="Login" %> using System; using System.Web; public class Login : IHttpHandler { public void ProcessRequest (HttpContext context) { context.Response.ContentType = "text/plain"; string username = context.Request.Form["name"]; string password = context.Request.Form["password"]; if( password == "aaaa" ) { System.Web.Security.FormsAuthentication.SetAuthCookie(username, false); context.Response.Write("OK"); } else { context.Response.Write("用戶名或密碼不正確。"); } } public bool IsReusable { get { return false; } } }
可以看到它僅僅是實(shí)現(xiàn)一個(gè)IHttpHandler接口而已,IHttpHandler接口也很簡單:
// 定義 ASP.NET 為使用自定義 HTTP 處理程序同步處理 HTTP Web 請求而實(shí)現(xiàn)的協(xié)定。 public interface IHttpHandler { // 獲取一個(gè)值,該值指示其他請求是否可以使用 System.Web.IHttpHandler 實(shí)例。 // // 返回結(jié)果: // 如果 System.Web.IHttpHandler 實(shí)例可再次使用,則為 true;否則為 false。 bool IsReusable { get;} // 通過實(shí)現(xiàn) System.Web.IHttpHandler 接口的自定義 HttpHandler 啟用 HTTP Web 請求的處理。 void ProcessRequest(HttpContext context); }
IsReusable屬性上面有注釋,我就不說了。接口中最重要的部分就是方法 void ProcessRequest(HttpContext context);這個(gè)方法簡單地不能再簡單,只有一個(gè)參數(shù),但這個(gè)參數(shù)的能量可不小,有了它幾乎就有了一切,這就是我對它的評價(jià)。關(guān)于HttpContext的更多詳細(xì)介紹請參考我的博客【我心目中的ASP.NET核心對象】。
在Login.ashx中,我做了三簡單的事:
1. 讀取輸入數(shù)據(jù): 從Request.Form中。
2. 執(zhí)行特定的業(yè)務(wù)邏輯: 一個(gè)簡單的判斷。
3. 返回結(jié)果給客戶端: 調(diào)用Response.Write()
是的,就是這三個(gè)簡單的操作,但也是絕大多數(shù)ashx文件的常規(guī)寫法,它的確可以完成一次請求的處理過程。
記住:事實(shí)上任何HttpHandler都是這樣處理請求的,只是有時(shí)會借助一些框架的包裝而變了味道而已。
我認(rèn)為:HttpHandler的強(qiáng)大離不開HttpContext,HttpHandler的重要性是因?yàn)楣芫€會將每個(gè)請求都映射到一個(gè)HttpHandler。
通常,我們需要新的HttpHandler,創(chuàng)建一個(gè)ashx文件就可以了。但也可以創(chuàng)建自己的HttpHandler,或者要將一類【特殊的路徑/擴(kuò)展名】交給某個(gè)處理器來處理,那么就需要我們在web.config中注冊那個(gè)處理器。
注意:如果是【特殊的擴(kuò)展名】可能還需要在IIS中注冊,原因很簡單:IIS不將請求交給ASP.NET,我們的代碼根本沒機(jī)會運(yùn)行!
我們可以采用以下方式在web.config中注冊一個(gè)自定義的處理器:
<httpHandlers> <add path="/MyService.axd" verb="*" validate="false" type="MySimpleServiceFramework.MyServiceHandler"/> </httpHandlers>
或者:(為了排版,我將一些代碼做了換行處理)
<httpHandlers> <remove verb="*" path="*.cs"/> <add verb="*" path="*.cs" validate="false" type="FishWebLib.Ajax.AjaxMethodV2Handler, FishWebLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=04db02423b9ebbb2"/>FishWebLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=04db02423b9ebbb2"/> <remove verb="*" path="*.ascx"/> <add verb="*" path="*.ascx" validate="false" type="FishWebLib.Ajax.UserControlHandler, FishWebLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=04db02423b9ebbb2"/>FishWebLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=04db02423b9ebbb2"/> </httpHandlers>
HttpModule
前面我已經(jīng)提到過HttpModule的工作方式:訂閱管線事件,并在事件處理器中執(zhí)行所需的相關(guān)操作。
這個(gè)描述看起來很平淡,但是,它的工作方式給了它無限強(qiáng)大的處理能力。
它的無限強(qiáng)大的處理能力來源于可以訂閱管線事件,因此,它有能力可以在許多階段修改請求,這些修改最終可能會影響請求的處理。
前面我說過:“HttpHandler是處理請求的主要對象”,但HttpModule卻可以隨意指定將某個(gè)請求交給某個(gè)處理器來執(zhí)行!
甚至,HttpModule也可以直接處理請求,完全不給HttpHandler工作的機(jī)會!
我們來看一下HttpModule的實(shí)現(xiàn)方式:
/// <summary> /// 能支持雙向GZIP壓縮的Module,它會根據(jù)客戶端是否啟用GZIP來自動(dòng)處理。 /// 對于服務(wù)來說,不用關(guān)心GZIP處理,服務(wù)只要處理輸入輸出就可以了。 /// </summary> internal class DuplexGzipModule : IHttpModule { public void Init(HttpApplication app) { app.BeginRequest += new EventHandler(app_BeginRequest); } void app_BeginRequest(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; // 注意:這里不能使用"Accept-Encoding"這個(gè)頭,二者的意義完全不同。 if( app.Request.Headers["Content-Encoding"] == "gzip" ) { app.Request.Filter = new GZipStream(app.Request.Filter, CompressionMode.Decompress); app.Response.Filter = new GZipStream(app.Response.Filter, CompressionMode.Compress); app.Response.AppendHeader("Content-Encoding", "gzip"); } } public void Dispose() { } }
每個(gè)HttpModule只需要實(shí)現(xiàn)IHttpModule接口就可以了。IHttpModule也是個(gè)簡單的接口:
public interface IHttpModule { void Dispose(); void Init(HttpApplication app); }
在這二個(gè)方法中,***個(gè)方法通??梢员3譃榭铡W钪匾姆椒ň褪荌nit,它給了HttpModule能訂閱管線事件的機(jī)會,然后在相應(yīng)的事件處理中,我們就可以執(zhí)行它的具體操作了。
還記得我在博客【我心目中的ASP.NET核心對象】***給出一個(gè)示例嗎?在QueryOrderService.ashx中,為了支持gzip,需要直接調(diào)用GZipStream類,對于一二個(gè)ashx來說,或許不是問題,如果這樣的處理器變多了,每個(gè)處理器都那樣寫,您能受得了嗎?反正我是受不了的,因此今天我把它改成使用Module來實(shí)現(xiàn),代碼簡單了許多。在本文末尾可以下載。
對于ASP.NET項(xiàng)目來說:當(dāng)您發(fā)現(xiàn)有很多處理輸入輸出的操作非常類似時(shí),那正是HttpModule可以發(fā)揮的舞臺,請把這些重復(fù)的操作交給它吧。
讓HttpModule工作也需要在web.config中注冊:
<httpModules> <add name="DuplexGzipModule" type="MySimpleServiceFramework.DuplexGzipModule"/> </httpModules>
通常,我會把一些HttpModule放在類庫中實(shí)現(xiàn),然后在需要使用的項(xiàng)目的web.config中注冊。
這也體現(xiàn)它的高重用性:寫一次,許多項(xiàng)目就可以直接使用。
HttpModule的加載方式:前面我說過“ASP.NET會為每個(gè)請求分配一個(gè)HttpApplication對象”,在每個(gè)HttpApplication對象的初始化操作中,它會加載所有在web.config中注冊的HttpModule。由于ASP.NET并不是只創(chuàng)建一個(gè)HttpApplication對象,而是多個(gè)HttpApplication對象,因此每個(gè)HttpModule的Init事件是有可能被多次調(diào)用的。許多人喜歡在這里做各類初始化的操作,那么請注意在這里修改靜態(tài)變量成員時(shí)的線程安全問題。特別地,如果要執(zhí)行程序初始化的操作,那么還是把代碼放在Global.asax的Application_Start中去處理吧, HttpModule的Init事件并不合適。
為HttpModule選擇訂閱合適的管線事件:這是非常重要的,訂閱不同的事件,產(chǎn)生的結(jié)果也會不一樣。原因也很簡單,在ASP.NET運(yùn)行環(huán)境中,并不只有一個(gè)HttpModule,某個(gè)HttpModule的判斷可能要依據(jù)其它HttpModule的輸出結(jié)果,而且在某些(晚期的)管線事件中,也不能再修改輸出數(shù)據(jù)。在后面的示例中,DirectProcessRequestMoudle訂閱了PostAuthorizeRequest事件,如果訂閱BeginRequest事件或許將得到更好的性能,但是,在BeginRequest事件中,身份認(rèn)證模塊還沒有工作,因此每個(gè)請求在這個(gè)事件階段都屬于“未登錄”狀態(tài)。
前面說了一大堆的HttpModule,事實(shí)上,在這個(gè)示例中,主角是另一個(gè)對象:Filter 。上篇博客我就提過它,***為了演示它,把它放在一個(gè)HttpHandler里【糟蹋了】,沒辦法,上篇的主題不是管線呀。今天只好和HttpModule一起出場了。我認(rèn)為Filter還是應(yīng)該和HttpModule一起使用才能發(fā)揮它的獨(dú)特價(jià)值。Filter的特點(diǎn)還真不合適在HttpHandler中使用,如果您在HttpHandler里使用Filter,我認(rèn)為有必要考慮一下是不是用錯(cuò)了。
借HttpModule的地盤我們來談?wù)凢ilter。Filter很低調(diào),低調(diào)到什么程度:可能很少有人關(guān)注過它,因此也少有人用過它。事實(shí)也確實(shí)如此,一般情況下可以不用它,但用到它,你會發(fā)現(xiàn)它非常強(qiáng)大。前面我經(jīng)常說到【輸入輸出流】,請求的數(shù)據(jù),除了請求頭以外,基本上全放在流中,如果您希望對這些數(shù)據(jù)以流的方式進(jìn)行處理,特別是希望對于所有請求,或者某類請求,那么使用Filter是非常恰當(dāng)?shù)?。前面的示例就是一個(gè)非常合理地使用,好好地品味它,或許您還能發(fā)現(xiàn)Filter能做更多的事情。
選 HttpHandler 還是 HttpModule ?
HttpHandler是每個(gè)請求的主要處理對象,而HttpModule可以選擇請求交給哪個(gè)HttpHandler來處理,甚至,它還可以選擇它自己來處理請求。
下面我給個(gè)示例代碼來說明HttpModule也能直接請求:
/// <summary> /// 此Module示范了直接使用Module也能處理客戶端的請求。 /// 建議:除非要很好的理由,否則不建議使用這種方法。 /// </summary> internal class DirectProcessRequestMoudle : IHttpModule { public void Init(HttpApplication app) { app.PostAuthorizeRequest += new EventHandler(app_PostAuthorizeRequest); } void app_PostAuthorizeRequest(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; ServiceInfo info = GetServiceInfo(app.Context); if( info == null ) return; ServiceExecutor.ProcessRequest(app.Context, info); app.Response.End(); }
為了更好的回答本節(jié)的這個(gè)問題,我再給段等效的代碼,不過,請求是經(jīng)過HttpHandler來處理
internal class MyServiceHandler : IHttpHandler { internal ServiceInfo ServiceInfo { get;set;} public void ProcessRequest(HttpContext context) { ServiceInfo info = this.ServiceInfo ?? GetServiceInfo(context); ServiceExecutor.ProcessRequest(context, info); }
HttpHandler和HttpModule都能處理請求,我該選哪個(gè)??
對于此類情況,我的答案是:視情況而定,正如我在注釋中描述的那樣,除非要很好的理由,否則不建議使用HttpModule處理請求。用HttpModule在某些時(shí)候可能會快點(diǎn),關(guān)鍵點(diǎn)在于處理完成時(shí)要調(diào)用Response.End();這會讓后面的事件全都短路,其它的HttpModule就沒有機(jī)會執(zhí)行。如果您的框架或者項(xiàng)目設(shè)計(jì)很依賴于管線中的事件處理,那么調(diào)用Response.End();無疑會破壞這個(gè)規(guī)則,也將會導(dǎo)致不能得到正確的結(jié)果。選擇HttpHandler就不會有這種事情發(fā)生。
不過,也沒有絕對的事情:在請求處理期間,您可以在任何地方調(diào)用Response.End(); 結(jié)果也是一樣的。
幸好,短路的情況并不經(jīng)常發(fā)生,因此選擇HttpHandler會讓整個(gè)ASP.NET的管線都能發(fā)揮作用,因此,我建議優(yōu)先選擇HttpHandler。
尤其是在HttpHandler能很好的完成工作的前提下,就應(yīng)該選HttpHandler,因?yàn)檫xHttpModule會給其它請求帶來不必要的性能損失,具體細(xì)節(jié)請繼續(xù)閱讀。
其實(shí),我們還可以從另一個(gè)角度來看這個(gè)問題。
首先,請仔細(xì)地閱讀前面的示例代碼,您是否發(fā)現(xiàn)它們在實(shí)現(xiàn)方式上非常類似?
現(xiàn)在應(yīng)該找到答案了吧:把具體的處理操作分離到HttpHandler,HttpModule之外的地方。那么,此時(shí)這個(gè)問題也就不是問題了,您也可以提供多種方案供使用者選擇。比如:我就為【我的服務(wù)框架】提供了5種方式讓使用者可以輕松地將一個(gè)C#方法公開為一個(gè)服務(wù)方法,該如何選擇這個(gè)問題,由使用者來決定,這個(gè)問題不會讓我為難。
我的觀點(diǎn):在沒有太多技術(shù)難度的前提下,提供多種解決辦法應(yīng)該是對的,您將會避開很多麻煩事情。
看不見的性能問題
前面我介紹了HttpModule的重要優(yōu)點(diǎn):高重用性。只要寫好一個(gè)HttpModule可以放在任何ASP.NET項(xiàng)目中使用,非常方便。
不過,再好的東西也不能濫用。HttpModule也可以對性能產(chǎn)生負(fù)面影響。原因也很簡單:對于每個(gè)ASP.NET請求,每個(gè)HttpModule都會在它們所訂閱的事件中,去執(zhí)行一些操作邏輯。這些操作邏輯或許對一些請求是無意義的,但仍會執(zhí)行。因此,計(jì)算機(jī)將會白白浪費(fèi)一些資源去執(zhí)行一些無意義的代碼。
知道了原因,解決辦法也就很清楚了:
1. 去掉不需要的HttpModule
2. 在每個(gè)HttpModule的事件處理器中,首先要確定是不是自己所需要處理的請求。對一些不相關(guān)的請求,應(yīng)該立即退出。
在我們創(chuàng)建一個(gè)ASP.NET項(xiàng)目時(shí),如果不做任何修改,微軟已經(jīng)為我們加載了好多HttpModule 。請看以下代碼:
protected void Page_Load(object sender, EventArgs e) { HttpApplication app = HttpContext.Current.ApplicationInstance; StringBuilder sb = new StringBuilder(); foreach( string module in app.Modules.AllKeys ) sb.AppendFormat(module).Append("<br />"); Response.Write(sb.ToString()); }
輸出結(jié)果如下:
總共有14個(gè)。
哎,大多數(shù)是我肯定不會用到的,但它們卻被加載了,因此,在它們所訂閱的事件中,它們的代碼將會檢查所有的請求,那些無意義的代碼將有機(jī)會執(zhí)行。如果您不想視而不見,那么請?jiān)趙eb.config中做類似的修改,將不需要的Module移除。
<httpModules> <remove name="Session"/> <remove name="RoleManager"/> </httpModules>
HttpModule的第2個(gè)需要注意的地方是:HttpModule對所有的請求有效,如果HttpModule不能處理所有的請求,那么請先判斷當(dāng)前請求是否需要處理,對于不需要處理的請求,應(yīng)該立即退出。請看以下示例代碼:
/// <summary> /// 【演示用】讓Aspx頁的請求支持gzip壓縮輸出 /// </summary> public class FishGzipModule : IHttpModule { public void Init(HttpApplication app) { app.BeginRequest += new EventHandler(app_BeginRequest); } void app_BeginRequest(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; // 這里做個(gè)簡單的演示,只處理aspx頁面的輸出壓縮。 // 當(dāng)然了,IIS也提供壓縮功能,這里也僅當(dāng)演示用,或許可適用于一些特殊場合。 if( app.Request.AppRelativeCurrentExecutionFilePath.EndsWith( "aspx", StringComparison.OrdinalIgnoreCase) == false ) // 注意:先判斷是不是要處理的請求,如果不是,直接退出。 // 而不是:先執(zhí)行了后面的判斷,再發(fā)現(xiàn)不是aspx時(shí)才退出。 return; string flag = app.Request.Headers["Accept-Encoding"]; if( string.IsNullOrEmpty(flag) == false &&flag.ToLower().IndexOf("gzip") >= 0 ) { app.Response.Filter = new GZipStream(app.Response.Filter, CompressionMode.Compress); app.Response.AppendHeader("Content-Encoding", "gzip"); } }
更多實(shí)戰(zhàn)介紹
本文從這里起,將不再過多的敘述一些理論文字,而是將以實(shí)戰(zhàn)的形式展示ASP.NET的強(qiáng)大管線功能,這些實(shí)戰(zhàn)展示了一些很經(jīng)典的應(yīng)用場景,其中大部分示例代碼將做為【我的服務(wù)框架】的關(guān)鍵部分。因此請注意理解這些代碼。
實(shí)戰(zhàn)代碼大量使用了上篇博客【我心目中的ASP.NET核心對象】所介紹的絕大多數(shù)對象,也算是再次展示那些核心對象的重要性,因此請務(wù)必先了解那些核心對象。
上篇博客僅展示了那些強(qiáng)大對象的功能,單獨(dú)使用它們,也是不現(xiàn)實(shí)的,今天,我將演示它們與HttpHandler, HttpModule一起并肩工作所能完成的各種任務(wù)。
故事未講完,傳奇在繼續(xù)。更多精彩即將上演!
實(shí)戰(zhàn)演示 - 模擬更多的HttpMethod
近幾年又有一種被稱為RESTful Web服務(wù)的概念進(jìn)入開發(fā)人員的視野,它提倡使用HTTP協(xié)議提供的GET、POST、PUT和DELETE方法來操作網(wǎng)絡(luò)資源。不過,目前的瀏覽器只支持GET、POST這二種方法,因此就有人想到采用HTTP頭,表單值,或者查詢字符串的形式來模擬這些瀏覽器不支持的HTTP方法。每種支持RESTful Web服務(wù)的框架都有它們自己的實(shí)現(xiàn)方式,今天我將使用HttpModule也來模擬這個(gè)操作。最終的結(jié)果是可以直接訪問HttpRequest.HttpMethod獲取這些操作的方法名字。
實(shí)現(xiàn)原理:訂閱管線中的BeginRequest事件,檢查當(dāng)前請求是否需要修改HttpMethod,如果是,則修改HttpMethod屬性。
所以選擇BeginRequest這個(gè)事件,是因?yàn)檫@個(gè)事件比較早,可以讓請求的后續(xù)階段都能讀到新的結(jié)果。
/// <summary> /// 【演示用】實(shí)現(xiàn)了模擬更多 HttpMethod 的Module /// </summary> internal class XHttpMethodModule : IHttpModule { private FieldInfo _field; public void Init(HttpApplication context) { // 訂閱這個(gè)較早的事件,可以讓請求的后續(xù)階段都能讀到新的結(jié)果。 context.BeginRequest += new EventHandler(context_BeginRequest); _field = typeof(HttpRequest).GetField("_httpMethod", BindingFlags.Instance | BindingFlags.NonPublic); } void context_BeginRequest(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; // 這里僅檢查是否為POST操作,如果您的應(yīng)用中需要使用GET來模擬的,請修改這里。 if( string.Equals(app.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase) ) { // 這里為了簡單,我只檢查請求頭,如果還需要檢查表單值或者查詢字符串,請修改這里。 string headerOverrideValue = app.Request.Headers["X-HTTP-Method-Override"]; if( string.IsNullOrEmpty(headerOverrideValue) == false ) { if( string.Equals(headerOverrideValue, "GET", StringComparison.OrdinalIgnoreCase) == false && string.Equals(headerOverrideValue, "POST", StringComparison.OrdinalIgnoreCase) == false ) { // HttpRequest.HttpMethod屬性其實(shí)就是訪問_httpMethod這個(gè)私有字段,我將直接修改它。 // 這樣修改后,最原始的HTTP方法就丟失,通常這或許也是可以接受的。 _field.SetValue(app.Request, headerOverrideValue.ToUpper()); } } } }
我認(rèn)為采用HttpModule來處理這個(gè)問題是個(gè)不錯(cuò)的選擇。它至少有2個(gè)好處:
1. 這個(gè)HttpModule能繼續(xù)給其它的網(wǎng)站項(xiàng)目使用,因此提高了代碼的重用性。
2. 我可以隨時(shí)決定要不要支持模擬,不需要模擬時(shí),從web.config中不加載它就可以了,因此切換很靈活,且不需要修改現(xiàn)有代碼。
來看一下頁面及調(diào)用結(jié)果吧
protected void Page_Load(object sender, EventArgs e) { Response.Write(Request.HttpMethod); }
調(diào)用結(jié)果如下:
實(shí)戰(zhàn)演示 - URL重寫
使用HttpModule來實(shí)現(xiàn)URL重寫。這個(gè)功能應(yīng)該是HttpModule非常經(jīng)典的應(yīng)用了。
通常情況下,這種應(yīng)用常用的方式是將一個(gè)URL: /product/12 重寫為 /product.aspx?id=12 ,此時(shí)product.aspx應(yīng)該是一個(gè)已經(jīng)已存在的頁面。顯然重寫后的地址更友好。URL重寫的目的就是能讓URL更友好。
實(shí)現(xiàn)原理:訂閱管線的PostAuthorizeRequest事件,檢查URL是不是期望修改的模式,如果是,則調(diào)用Context.RewritePath()完成URL的重寫操作。在管線的后續(xù)處理中,最終會使用新的URL來映射到一個(gè)合適的HttpHandler。說明:選擇的事件只要在【第10個(gè)事件】之前就可以了,因?yàn)樵诘?0個(gè)事件前重寫URL,才能保證到將請求映射到合適的處理器來執(zhí)行。就這么簡單,請參考以下代碼:
public class MyServiceUrlRewriteModule : IHttpModule { // 為了演示簡單,直接寫死地址。 // 注意:MyService.axd 必須在web.config中注冊,以保證它能成功映射。 public static string RewriteUrlPattern = "/MyService.axd?sc={1}&op={1}"; public void Init(HttpApplication app) { app.PostAuthorizeRequest += new EventHandler(app_PostAuthorizeRequest); } void app_PostAuthorizeRequest(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; // 這里將檢查URL是否為需要重寫的模式,比如: // http://localhost:11647/service/OrderService/QueryOrder NamesPair pair = FrameworkRules.ParseNamesPair(app.Request); if( pair == null ) return; // 開始重寫URL,***將會映射到MyServiceHandler int p = app.Request.Path.IndexOf('?'); if( p >0 ) app.Context.RewritePath(string.Format(RewriteUrlPattern, pair.ServiceName, pair.MethodName) + "&" + app.Request.Path.Substring(p + 1) ); else app.Context.RewritePath(string.Format(RewriteUrlPattern, pair.ServiceName, pair.MethodName)); }
重寫發(fā)生了什么?
對于一個(gè)傳入請求:http://localhost:11647/service/FormDemoService/ShowUrlInfo
它將被重寫為:http://localhost:11647/MyService.axd?sc=FormDemoService&op=ShowUrlInfo
由于在web.config中,對MyService.axd已做過注冊,因此ASP.NET會將請求轉(zhuǎn)交給注冊的處理器來處理它。
注意:URL重寫,會影響某些變量的值。請參考以下代碼,我將寫個(gè)服務(wù)方法來檢測這個(gè)現(xiàn)象:
[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();
}
輸出結(jié)果:
實(shí)戰(zhàn)演示 - URL路由
使用HttpModule來實(shí)現(xiàn)URL路由。這個(gè)功能隨著ASP.NET MVC框架的出現(xiàn)也逐漸流行起來了。
URL路由的目標(biāo)也是為了使用URL更友好,與URL重寫類似。
實(shí)現(xiàn)原理:訂閱管線的PostResolveRequestCache事件,檢查URL是不是期望的路由模式,如果是,則要根據(jù)請求中所包含的信息找到一個(gè)合適的處理器,并臨時(shí)保存這個(gè)處理器,重寫URL到一個(gè)ASP.NET能映射處理器的地址。在管線的PostMapRequestHandler中,檢查前面有沒有臨時(shí)保存的處理器,如果有,則重新給Context.Handler賦值,并重寫URL到原始地址。在管線的后續(xù)處理中,最終會使用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重寫的根本差別。
// 即:根據(jù)當(dāng)前請求【主動(dòng)】尋找一個(gè)處理器,而不是使用RewritePath讓ASP.NET替我們?nèi)フ摇?/p>
MyServiceHandler handler = GetHandler(app.Context);
if( handler == null )
return;
// 臨時(shí)保存前面獲取到的處理器,這個(gè)值將在PostMapRequestHandler事件中再取出來。
app.Context.Items[s_dataKey] = handler;
// 進(jìn)入正常的MapRequestHandler事件,隨便映射到一個(gè)處理器就行了。
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);
// 還原根據(jù)GetHandler(app.Context)調(diào)用得到的處理器。
// 因?yàn)榇藭r(shí)app.Context.Handler是由"~/MyServiceUrlRoutingModule.axd"映射得到的。
app.Context.Handler = handler;
}
}
注意:在MyServiceUrlRoutingModule中,我將請求【路由】到一個(gè)MyServiceHandler的實(shí)例,而不是讓ASP.NET根據(jù)URL來替我選擇。
在URL重寫的演示中,有些URL相關(guān)的屬性發(fā)生了改變,我們再來看一下URL路由是個(gè)什么結(jié)果:
實(shí)現(xiàn)自己的服務(wù)框架
本篇博客在開頭說過:將在本次博客中改進(jìn)上次的服務(wù)實(shí)現(xiàn),讓它成為一個(gè)真正能用的服務(wù)框架。
前面在講述ASP.NET管線時(shí),給出了很多示例代碼,這些示例代碼都可以在博客的結(jié)尾處下載到。這些代碼來源于【我的服務(wù)框架】中的部分源代碼,下面我將重點(diǎn)介紹【我的服務(wù)框架】。
利用【我的服務(wù)框架】將類公開成服務(wù)
在【我的服務(wù)框架】中,一個(gè)類要想公開為服務(wù)類,并不需要繼承某個(gè)類或者實(shí)現(xiàn)什么接口,只需要在類上加一個(gè)特性就好了,方法也只需加一個(gè)特性,示例代碼如下:
[MyService]
public class OrderService
{
[MyServiceMethod]
public static string Hello(string name)
{
return "Hello " + name;
}
[MyServiceMethod]
public List<Order>QueryOrder(QueryOrderCondition query)
{
// 模擬查詢過程,這里就直接返回一個(gè)列表。
List<Order>list = new List<Order>();
for( int i = 0;i <10;i++ )
list.Add(DataFactory.CreateRandomOrder());
return list;
}
public string HiddenMethod(string aa)
{
// 這個(gè)方法應(yīng)該是不能以服務(wù)方式被調(diào)用到的。
throw new NotImplementedException();
}
}
如果某個(gè)方法需要只公開給登錄用戶或者指定的用戶,還可以使用以下方式:
// 這是一個(gè)訪問受限的服務(wù)類,只允許某些用戶調(diào)用。
[Authorize]
[MyService]
public static class LimitService
{
[Authorize(Users="fish-li, cc")]
[MyServiceMethod]
public static string CalcPassword(string pwd)
{
// 這個(gè)方法只能由 fish-li, cc 二個(gè)用戶來調(diào)用
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)
{
// 這個(gè)方法只能由已登錄用戶調(diào)用。
if( string.IsNullOrEmpty(str) )
return string.Empty;
return Convert.ToBase64String(Encoding.UTF8.GetBytes(str));
}
}
就這么簡單,一個(gè)類,就可以成為一個(gè)服務(wù)。
說明:本框架并不要求將服務(wù)類在網(wǎng)站項(xiàng)目中實(shí)現(xiàn),完全可以放在類庫中實(shí)現(xiàn)。
還可以支持Session哦。
[MyService(SessionMode=SessionMode.Support)]
public class SessionDemoService
{
[MyServiceMethod]
public int Add(int a)
{
// 一個(gè)累加的方法,檢驗(yàn)是否可以訪問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
}
【我的服務(wù)框架】支持的序列化的種類
在上篇博客中,我演示了使用JSON序列化的做法來實(shí)現(xiàn)一個(gè)服務(wù)響應(yīng)。本來也是打算讓框架僅支持JSON序列化的,因?yàn)閭鬏數(shù)臄?shù)據(jù)量小嘛。沒想到,做到后來,還是認(rèn)為有必要把XML序列化也加進(jìn)來,XML序列化快呀。***,居然想到既然是服務(wù)框架,Ajax調(diào)用也能算是服務(wù)吧,總不能不支持吧,后來干脆也能支持部分的Ajax調(diào)用了。
在【我的服務(wù)框架】中,服務(wù)端判斷客戶端發(fā)送的數(shù)據(jù)序列化方式是通過判斷請求頭"Serializer-Format"來實(shí)現(xiàn)的。序列化的種類還允許繼續(xù)自定義。只要實(shí)現(xiàn)以下接口:
public interface ISerializerProvider
{
object Deserialize(Type destType, HttpRequest request);
void Serializer(object obj, HttpResponse response);
}
然后調(diào)用以下方法就可以了:
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>
/// 此委托用來判斷客戶端發(fā)起的請求中,數(shù)據(jù)是以什么方式序列化的。
/// 返回的結(jié)果將會交給SerializerProviderFactory.GetSerializerProvider()來獲取序列化提供者
/// 默認(rèn)的實(shí)現(xiàn)是檢查請求頭:"Serializer-Format"
/// </summary>
public static Func<HttpRequest, string>GetSerializerFormat
{
internal get { return _serializerFormatRule; }
set
{
if( value == null )
throw new ArgumentNullException("value");
_serializerFormatRule = value;
}
}
只是一個(gè)委托,可以自己重新實(shí)現(xiàn)。
目前本框架提供了三個(gè)實(shí)現(xiàn)了接口ISerializerProvider的類供用戶使用:JsonSerializerProvider, XmlSerializerProvider, FormSerializerProvider
這里只展示JsonSerializerProvider的實(shí)現(xiàn):
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的實(shí)現(xiàn)不夠完善,因?yàn)樵俑阆氯ィ秃汀疚业腤EB框架】就重復(fù)了。有興趣的自己去完善吧。
這里再給自己的作品打個(gè)廣告:
【ASP.NET MVC 框架,我也來山寨一下】, 【曬曬我的Ajax服務(wù)端框架】, 【我的Ajax服務(wù)端框架 - (1) JS直接調(diào)用C#方法】
【我的服務(wù)框架】對gzip的支持
對于gzip的支持,我只想說:太簡單了。
前面不是已給出DuplexGzipModule的實(shí)現(xiàn)代碼嘛。是的,就是把它注冊到web.config中就可以了。
你說簡不簡單? 完全不用寫多余的代碼,要不要gzip支持,也只是個(gè)配置問題!
說到這里,我想起前段時(shí)間Artech寫的一篇博客通過WCF擴(kuò)展實(shí)現(xiàn)消息壓縮,正如我在前篇博客的回復(fù)中說到的:“本來真沒興趣看的,不過,為了驗(yàn)證我的猜想,還是去看了一下,果然也沒讓我失望。”。
在此,有必要公開一下我的想法:絕對沒有半點(diǎn)看不起Artech的意思,只是我對WCF沒有興趣了。理由也簡單:不夠簡單。
還是接著說,Artech的博客展示了在WCF中壓縮消息的方式,當(dāng)然我相信Artech對于WCF的理解,他的方案或許應(yīng)該是最簡單的解決方案,但是和【我的服務(wù)框架】對gzip的支持的易用性根本沒法比。
WCF的粉絲們,當(dāng)您看到這里,請先別忙著噴我。聽我說完:WCF的確很強(qiáng)大,我的這個(gè)不到700行的框架那也是根本不能和它相比的。
做這個(gè)比較僅僅是為了展示ASP.NET是一個(gè)強(qiáng)大的平臺,ASP.NET有更高水準(zhǔn)的擴(kuò)展性。
利用【我的服務(wù)框架】發(fā)布服務(wù)的5種方式
【我的服務(wù)框架】可以提供5種不同的方式,讓您將一個(gè)類及方法公開成一個(gè)服務(wù),供外界調(diào)用。
方法1:使用DirectProcessRequestMoudle,只需要配置web.config即可。
<httpModules>
<add name="DirectProcessRequestMoudle" type="MySimpleServiceFramework.DirectProcessRequestMoudle"/>
</httpModules>
客戶端調(diào)用URL:http://localhost:11647/service/OrderService/QueryOrder
說明:URL模式是可以自由定義的,只要給FrameworkRules.ParseNamesPair賦值即可,它的定義如下:
public static Func<HttpRequest, NamesPair>ParseNamesPair
默認(rèn)的實(shí)現(xiàn)方式:
internal static class UrlPatternHelper
{
// 為了演示簡單,我只定義一個(gè)URL模式。【因?yàn)槲艺J(rèn)為對于服務(wù)來說,一個(gè)就夠了】
// 如果希望適用性更廣,可以從配置文件中讀取,并且可支持多組URL模式。
// URL中加了"/service/"只是為了能更好地區(qū)分其它請求,如果您的網(wǎng)站沒有子目錄,刪除它也是可以的。
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}")
};
}
客戶端調(diào)用URL: http://localhost:11647/service/OrderService/QueryOrder
方法2:使用MyServiceUrlRoutingModule,只需要配置web.config即可。
<httpModules>
<add name="MyServiceUrlRoutingModule" type="MySimpleServiceFramework.MyServiceUrlRoutingModule"/>
</httpModules>
客戶端調(diào)用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>
客戶端調(diào)用URL: http://localhost:11647/service/OrderService/QueryOrder
方法4:使用MyServiceHandler,只需要配置web.config即可。
<httpHandlers>
<add path="/MyService.axd" verb="*" validate="false" type="MySimpleServiceFramework.MyServiceHandler"/>
</httpHandlers>
客戶端調(diào)用URL: http://localhost:11647/MyService.axd?sc=OrderService&op=QueryOrder
方法5:創(chuàng)建一個(gè)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;
}
}
}
客戶端調(diào)用URL: http://localhost:11647/MyService.ashx?sc=OrderService&op=QueryOrder
注意:前三種方法,需要在IIS中做些額外的配置,因?yàn)閁RL中不包含文件擴(kuò)展名了,IIS不知道把請求交給ASP.NET來處理。
具體配置見下圖,此處省略78個(gè)字。
我對發(fā)布服務(wù)的5種方式的建議
雖然,我給出了5種發(fā)布方式,但是我還是想說說我個(gè)人的想法。
在這些方法中,使用URL重寫,URL路由的方法,并不是我想推薦的,寫它們是主要是為了展示HttpModule 。不推薦它們是因?yàn)樗鼈円袛郩RL是否符合指定模式,這個(gè)判斷是有成本的。至于成本有多高,特此,我專了做門的測試。在示例代碼壓縮包中有個(gè)___TestRoutePerformance目錄,結(jié)果如何,還是您自己去看吧,我也有點(diǎn)累了。
此外,我想問:對于服務(wù)來說,URL友好有多大意義?服務(wù)的URL會讓用戶來輸入還是讓Google的爬蟲來訪問?
如果以上二個(gè)問題都是否定的,那么,這二種方法就是在白白浪費(fèi)機(jī)器的性能了。
當(dāng)然了,如果您的站點(diǎn)訪問量不大,那么這點(diǎn)性能也可以忽略不計(jì)了,就當(dāng)我沒說。
使用URL重寫URL路由,還有個(gè)比較麻煩的事情:如果想通過URL多傳遞一個(gè)參數(shù),那么,是不是又要修改URL模式?
對于使用DirectProcessRequestMoudle這種模式,我以前已經(jīng)說過了:除非要很好的理由,否則不建議使用這種方法。
至于其它的二種方式,本質(zhì)上是一樣的,只是說:處理器誰來寫的差別了。
不過,如果您要是選擇手工創(chuàng)建一個(gè)處理器,除了不用修改web.config之外,還可以自定義URL參數(shù)名,可以選擇要不要支持Session
【我的服務(wù)框架】的一些核心類
ReflectionHelper類用于根據(jù)類名及服務(wù)名定位到一個(gè)服務(wù)類型以及要調(diào)用的方法。
因此,它在框架中的作用也是非常關(guān)鍵的。
internal static class ReflectionHelper
{
private static List<TypeAndAttrInfo>s_typeList;
static ReflectionHelper()
{
InitServiceTypes();
}
/// <summary>
/// 加載所有的服務(wù)類型,判斷方式就是檢查類型是否有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>
/// 根據(jù)一個(gè)名稱獲取對應(yīng)的服務(wù)類型(從緩存中獲取類型)
/// </summary>
/// <param name="typeName"></param>
/// <returns></returns>
private static TypeAndAttrInfo GetServiceType(string typeName)
{
if( string.IsNullOrEmpty(typeName) )
throw new ArgumentNullException("typeName");
// 查找類型的方式:如果有點(diǎn)號,則按全名來查找(包含命名空間),否則只看名字。
// 本框架對于多個(gè)匹配條件的類型,將返回***個(gè)匹配項(xiàng)。
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>
/// 根據(jù)指定的類型以及方法名稱,獲取對應(yīng)的方法信息
/// </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;
// 由于服務(wù)方法的參數(shù)來源于反序列化,此時(shí)只可能包含一個(gè)參數(shù)。
ParameterInfo[] paraInfos = method.GetParameters();
if( paraInfos.Length != 1 )
throw new ArgumentNullException("指定的方法雖找到,但該方法的參數(shù)數(shù)量不是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>
/// 根據(jù)類型名稱以及方法名稱返回要調(diào)用的相關(guān)信息
/// </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用于調(diào)用服務(wù)方法,前面所說的5種服務(wù)發(fā)布方式,最終都要經(jīng)過這里。
/// <summary>
/// 最終調(diào)用服務(wù)方法的工具類。
/// </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("沒有調(diào)用數(shù)據(jù),請將調(diào)用數(shù)據(jù)以請求體的方式傳入。");
if( info.InvokeInfo.AuthenticateRequest(context) == false )
ExceptionHelper.Throw403Exception(context);
// 獲取客戶端的數(shù)據(jù)序列化格式。
// 默認(rèn)實(shí)現(xiàn)方式:request.Headers["Serializer-Format"];
// 注意:這是我自定義的請求頭名稱,也可以不指定,默認(rèn)為:form (表單)
string serializerFormat = FrameworkRules.GetSerializerFormat(context.Request);
ISerializerProvider serializerProvider =
SerializerProviderFactory.GetSerializerProvider(serializerFormat);
// 獲取要調(diào)用方法的參數(shù)類型
Type destType = info.InvokeInfo.MethodAttrInfo.ParamType;
// 獲取要調(diào)用的參數(shù)
context.Request.InputStream.Position = 0;// 防止其它Module讀取過,但沒有歸位。
object param = serializerProvider.Deserialize(destType, context.Request);
// 調(diào)用服務(wù)方法
object result = info.InvokeInfo.MethodAttrInfo.MethodInfo.Invoke(
info.InvokeInfo.ServiceInstance, new object[] { param });
// 寫輸出結(jié)果
if( result != null )
serializerProvider.Serializer(result, context.Response);
}
/// <summary>
/// 【外部接口】用于根據(jù)服務(wù)的類名和方法名執(zhí)行某個(gè)請求
/// </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);
}
}
到此,關(guān)于“怎么用ASP.NET寫服務(wù)框架”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。