ASP,NET核心對象_第1頁
ASP,NET核心對象_第2頁
ASP,NET核心對象_第3頁
ASP,NET核心對象_第4頁
ASP,NET核心對象_第5頁
已閱讀5頁,還剩17頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、淺談ASP.NET核心對象想當(dāng)初在只使用WebForms框架并以服務(wù)端為中心的開發(fā)模式時(shí),發(fā)現(xiàn)ASP.NET好復(fù)雜。一大堆服務(wù)端控件,各有各的使用方法,有些控件的事件也很重要,必須在合適地時(shí)機(jī)去響應(yīng),還真有些復(fù)雜。后來逐漸發(fā)現(xiàn)這些復(fù)雜的根源其實(shí)就是服務(wù)器控件相關(guān)的抽象邏輯。隨著Ajax越用越多,可能有些人也做過這些事情:【新建一個(gè)ashx文件,讀取一些用戶的輸入數(shù)據(jù),F(xiàn)orm, QueryString,然后調(diào)用業(yè)務(wù)邏輯代碼,將處理后的結(jié)果序列化成JSON字符串再發(fā)給客戶端】,這樣也能完成一次請求。不知大家有沒有做過這類事情,反正我是做過的。慢慢地,我也嫌煩了,這些事情中除了調(diào)用業(yè)務(wù)邏輯部分,都

2、是些體力活嘛。于是想,寫點(diǎn)代碼把這些事情交給它們?nèi)プ霭?,我只處理與請求有關(guān)的數(shù)據(jù)處理就好了。終于,我寫了個(gè)簡陋的框架,并自稱為【我的Ajax服務(wù)端框架】以及【我的MVC框架】。寫完這些東西后,發(fā)現(xiàn)ASP.NET的東西變少了,但是仍可以實(shí)現(xiàn)很多功能。其實(shí),我們可以從另一角度來看ASP.NET,它就是一個(gè)底層框架平臺(tái),它負(fù)責(zé)接收HTTP請求(從IIS傳入),將請求分配給一個(gè)線程,再把請求放到它的處理管道中,由一些其它的【管道事件訂閱者】來處理它們,最后將處理結(jié)果返回給客戶端。而WebForms或者M(jìn)VC框架,都屬于ASP.NET平臺(tái)上的【管道事件訂閱者】而已,Web Service也是哦。如果你不

3、想受限于WebForms或者M(jìn)VC框架,或者您還想用ASP.NET做點(diǎn)其它的事情,比如:自己的服務(wù)框架,就像WebService那樣。但希望用其它更簡單的序列化方式來減少網(wǎng)絡(luò)流量,或者還有加密要求。那么了解ASP.NET提供了哪些功能就很有必要了。本文將站在ASP.NET平臺(tái)的角度,來看看ASP.NET的一些基礎(chǔ)功能。雖然不會(huì)涉及任何其它上層框架,但所講述的內(nèi)容其實(shí)是適合其它上層框架的。前面我說到:ASP.NET負(fù)責(zé)接收請求,并將請求分配給一個(gè)線程來執(zhí)行。最終執(zhí)行什么呢?當(dāng)然就是我們的處理邏輯。但我們在處理時(shí),用戶輸入的數(shù)據(jù)又是從哪里來的呢?只能是HTTP請求。但它又可分為二個(gè)部分:請求頭和請

4、求體。在ASP.NET中,我們并不需要去分析請求頭和請求體,比如:我們可以直接訪問QueryString,F(xiàn)orm就可以得到用戶傳過來的數(shù)據(jù)了,然而QueryString其實(shí)是放在請求頭上,在請求頭上的還有Cookie,F(xiàn)orm以及PostFile則放在請求體中。如果對這些內(nèi)容不清楚的可以參考我的博客:【細(xì)說Cookie】和【細(xì)說 Form (表單)】。在我這二篇博客中,您應(yīng)該可以看出:要是讓您從請求頭請求體中讀取這些數(shù)據(jù),還是很麻煩的。幸好,ASP.NET做為底層平臺(tái),在每次處理請求時(shí),都將這些數(shù)據(jù)轉(zhuǎn)成方便我們處理的對象了。今天我將只談這些基礎(chǔ)對象以及它們可以實(shí)現(xiàn)的功能。在我的眼里,ASP.

5、NET有三大核心對象:HttpContext, HttpRequest, HttpResponse。除此之外,還有二個(gè)對象雖然稱不上核心,但仍然比較重要:HttpRuntime,HttpServerUtility事實(shí)上,這些類的實(shí)例在其它的一些類型中也經(jīng)常被引用到,從出現(xiàn)的頻率也可以看出它們的重要性。中國人喜歡把較重要的東西放在最后,做為壓軸出場。今天我也將按照這個(gè)風(fēng)俗習(xí)慣做為這些對象的出場順序來分別說說它們有哪些【重要的功能】。HttpRuntime第一個(gè)出場的是HttpRuntime,其實(shí)這個(gè)對象算是整個(gè)ASP.NET平臺(tái)最核心的對象,從名字可以看出它的份量。但它包含的很多方法都不是pub

6、lic類型的,它在整個(gè)請求的處理過程中,做了許多默默無聞但非常重要的工作。反而公開的東西并不多,因此需要我們掌握的東西也較少。不能讓它做為壓軸出場就讓它第一個(gè)出場吧。這就是我的想法。HttpRuntime公開了一個(gè)方法靜態(tài) UnloadAppDomain() ,這個(gè)方法可以讓我們用代碼重新啟動(dòng)網(wǎng)站。通常用于用戶通過程序界面修改了一個(gè)比較重要的參數(shù),這時(shí)需要重啟程序了。HttpRuntime還公開了一個(gè)大家都熟知的靜態(tài)屬性 Cache ??赡苡行┤苏J(rèn)為他/她在使用Page.Cache或者HttpContext.Cache,事實(shí)上后二個(gè)屬性都是HttpRuntime.Cache的【快捷方式】。Ht

7、tpRuntime.Cache是個(gè)非常強(qiáng)大的東西,主要用于緩存一些數(shù)據(jù)對象,提高程序性能。雖然緩存實(shí)現(xiàn)方式比較多,一個(gè)static變量也算是能起到緩存的作用,但HttpRuntime.Cache的功能絕不僅限于一個(gè)簡單的緩存集合,如果說實(shí)現(xiàn)“緩存項(xiàng)的滑動(dòng)過期和絕對過期”算是小兒科的話,緩存依賴的功能應(yīng)該可以算是個(gè)強(qiáng)大的特性吧。更有意義的是:它緩存的內(nèi)容還可以在操作系統(tǒng)內(nèi)存不足時(shí)能將一些緩存項(xiàng)釋放(可指定優(yōu)先級(jí)),從而獲得那些對象的內(nèi)存,并能在移除這些緩項(xiàng)時(shí)能通知您的代碼??赡苡腥苏J(rèn)為當(dāng)內(nèi)存不足時(shí)自動(dòng)釋放一些緩存對象容易啊,使用WeakReference類來包裝一下就可以了。但WeakRefer

8、ence不提供移除時(shí)的通知功能。HttpRuntime.Cache還有個(gè)非常酷的功能是:它并非只能在ASP.NET環(huán)境中使用,也能在其它編程模型中使用,比如大家熟知的WinForm編程模型。如何使用呢,直接訪問HttpRuntime.Cache這個(gè)靜態(tài)屬性肯定是不行的。我們只要在程序初始化時(shí)創(chuàng)建一個(gè)HttpRuntime的實(shí)例,當(dāng)然還要保證它不會(huì)被GC回收掉。然后就可以像在ASP.NET中一樣使用HttpRuntime.Cache了,就這么簡單。是的,就是這樣簡單,您就可以在其它編程模型中使用Cache的強(qiáng)大功能:線程安全的集合,2種過期時(shí)間的選擇,緩存依賴,內(nèi)存不足時(shí)自動(dòng)釋放且有回調(diào)通知。這

9、里我還想說說緩存依賴。我曾經(jīng)見過一個(gè)使用場景:有人從一堆文件(分為若干類別)中加載數(shù)據(jù)到Cache中,但是他為了想在這些數(shù)據(jù)文件修改時(shí)能重新加載,而采用創(chuàng)建線程并輪詢文件的最后修改時(shí)間的方式來實(shí)現(xiàn),總共開了60多個(gè)線程,那些線程每隔15去檢查各自所“管轄”的文件是否已修改。如果您也是這樣處理的,我今天就告訴您:真的沒必要這么復(fù)雜,您只要在添加緩存項(xiàng)時(shí)創(chuàng)建一個(gè)CacheDependency的實(shí)例并調(diào)用相應(yīng)的重載方法就可以了。具體CacheDependency有哪些參數(shù),您還是參考一下MSDN吧。這里我只告訴您:它能在一個(gè)文件或者目錄,或者多個(gè)文件在修改時(shí),自動(dòng)通知Cache將緩存項(xiàng)清除,而且還可

10、以設(shè)置到依賴其它的緩存項(xiàng),甚至能將這些依賴關(guān)系組合使用,非常強(qiáng)大。可能還有人會(huì)擔(dān)心往Cache里放入太多的東西會(huì)不會(huì)影響性能,因此有人還想到控制緩存數(shù)量的辦法。我只想說:緩存容器決定一個(gè)對象的保存位置是使用Hash算法的,并不會(huì)因?yàn)榫彺骓?xiàng)變多而影響性能,更有趣的是ASP.NET的Cache的容器還并非只有一個(gè),它能隨著CPU的數(shù)量而調(diào)整,看這個(gè)架式,應(yīng)該在設(shè)計(jì)Cache時(shí)還想到了高并發(fā)訪問的性能問題。如果這時(shí)你還在統(tǒng)計(jì)緩存數(shù)量并手工釋放某些緩存項(xiàng),我只能說您在寫損害性能的代碼。HttpServerUtility , HttpUtility不要覺得奇怪,這次我一下子請了二個(gè)對象出場了。由于Htt

11、pServerUtility的實(shí)例通常以Server的屬性公開,但它的提供一些Encode, Decode方法其實(shí)調(diào)用的是HttpUtility類的靜態(tài)方法。所以我就把它們倆一起請出來了。HttpUtility公開了一些靜態(tài)方法,如:HtmlEncode(),應(yīng)該是使用頻率比較高的方法,用于防止注入攻擊,它負(fù)責(zé)安全地生成一段HTML代碼。有時(shí)我們還需要生成一個(gè)URL,那么UrlEncode()方法就能派上用場了,因?yàn)閁RL中并不能包含所有字符,所以要做相應(yīng)的編碼。HttpUtility還有一個(gè)方法HtmlAttributeEncode(),它也是用于防止注入攻擊,安全地輸出一個(gè)HTML屬性。在

12、.net4中,HttpUtility還提供了另一個(gè)方法:JavaScriptStringEncode(),也是為了防止注入攻擊,安全地在服務(wù)端輸出一段JS代碼。HttpUtility還公開了一些靜態(tài)方法,如:HtmlDecode(), UrlDecode(),通常來說,我們并不需要使用它們。尤其是UrlDecode ,除非您要自己的框架,一般來說,在我們訪問QueryString, Form時(shí),已經(jīng)做過UrlDecode了,您就不用再去調(diào)用了。HttpServerUtility除了公開了比較常用的Encode, Decode方法外,還公開了一個(gè)非常有用的方法:Execute(),是的,它非常有

13、用,尤其是您需要在服務(wù)端獲取一個(gè)頁面或者用戶控件的HTML輸出時(shí)。如果您對這個(gè)功能有興趣可以參考我的博客:【我的Ajax服務(wù)端框架 - (4) JS直接請求ascx用戶控件】HttpRequest現(xiàn)在總算輪到第一個(gè)核心對象出場了。MSDN給它作了一個(gè)簡短的解釋:“使 ASP.NET 能夠讀取客戶端在 Web 請求期間發(fā)送的 HTTP 值。”這個(gè)解釋還算是到位的。HttpRequest的實(shí)例包含了所有來自客戶端的所有數(shù)據(jù),我們可以把這些數(shù)據(jù)看成是輸入數(shù)據(jù), Handler以及Module就相當(dāng)于是處理過程,HttpResponse就是輸出了。在HttpRequest包含的所有輸入數(shù)據(jù)中,有我們經(jīng)

14、常使用的QueryString, Form, Cookie,它還允許我們訪問一些HTTP請求頭、瀏覽器的相關(guān)信息、請求映射的相關(guān)文件路徑、URL詳細(xì)信息、請求的方法、請求是否已經(jīng)過身份驗(yàn)證,是否為SSL等等。HttpRequest的公開屬性絕大部分都是比較重要的,這里就簡單地列舉一下吧。/獲取服務(wù)器上ASP.NET應(yīng)用程序的虛擬應(yīng)用程序根路徑。 publicstringApplicationPathget; /獲取應(yīng)用程序根的虛擬路徑,并通過對應(yīng)用程序根使用波形符()表示法(例如,以“/page.aspx”的形式)使該路徑成為相對路徑。 publicstringAppRelativeCurre

15、ntExecutionFilePathget; /獲取或設(shè)置有關(guān)正在請求的客戶端的瀏覽器功能的信息。 publicHttpBrowserCapabilitiesBrowserget;set; /獲取客戶端發(fā)送的cookie的集合。 publicHttpCookieCollectionCookiesget; /獲取當(dāng)前請求的虛擬路徑。 publicstringFilePathget; /獲取采用多部分MIME格式的由客戶端上載的文件的集合。 publicHttpFileCollectionFilesget; /獲取或設(shè)置在讀取當(dāng)前輸入流時(shí)要使用的篩選器。 publicStreamFilterge

16、t;set; /獲取窗體變量集合。 publicNameValueCollectionFormget; /獲取HTTP頭集合。 publicNameValueCollectionHeadersget; /獲取客戶端使用的HTTP數(shù)據(jù)傳輸方法(如GET、POST或HEAD)。 publicstringHttpMethodget; /獲取傳入的HTTP實(shí)體主體的內(nèi)容。 publicStreamInputStreamget; /獲取一個(gè)值,該值指示是否驗(yàn)證了請求。 publicboolIsAuthenticatedget; /獲取當(dāng)前請求的虛擬路徑。 publicstringPathget; /獲取

17、HTTP查詢字符串變量集合。 publicNameValueCollectionQueryStringget; /獲取當(dāng)前請求的原始URL。 publicstringRawUrlget; /獲取有關(guān)當(dāng)前請求的URL的信息。 publicUriUrlget; /從QueryString、Form、Cookies或ServerVariables集合中獲取指定的對象。 publicstringthisstringkeyget; /將指定的虛擬路徑映射到物理路徑。 /參數(shù):virtualPath:當(dāng)前請求的虛擬路徑(絕對路徑或相對路徑)。 /返回結(jié)果:由virtualPath指定的服務(wù)器物理路徑。 p

18、ublicstringMapPath(stringvirtualPath);下面我來說說一些不被人注意的細(xì)節(jié)。HttpRequest的QueryString, Form屬性的類型都是NameValueCollection,它個(gè)集合類型有一個(gè)特點(diǎn):允許在一個(gè)鍵下存儲(chǔ)多個(gè)字符串值。以下代碼演示了這個(gè)特殊的現(xiàn)象:protectedvoidPage_Load(objectsender,EventArgse) stringallkeys=Request.QueryString.AllKeys; if(allkeys.Length=0) Response.Redirect( Request.RawUrl+

19、?aa=1&bb=2&cc=3&aa=+HttpUtility.UrlEncode(5,6,7),true); StringBuildersb=newStringBuilder(); foreach(stringkeyinallkeys) sb.AppendFormat(0=1, HttpUtility.HtmlEncode(key),HttpUtility.HtmlEncode(Request.QueryStringkey); this.labResult.Text=sb.ToString(); 頁面最終顯示結(jié)果如下(注意鍵值為aa的結(jié)果):說明:1. HttpUtility.ParseQu

20、eryString(string)這個(gè)靜態(tài)方法能幫助我們解析一個(gè)URL字符串,返回的結(jié)果也是NameValueCollection類型。2. NameValueCollection是一個(gè)不區(qū)分大小寫的集合。HttpRequest有一個(gè)Cookies屬性,MSDN給它的解釋是:“獲取客戶端發(fā)送的 Cookie 的集合。”,這次MSDN的解釋就不完全準(zhǔn)確了。請看如下代碼:protectedvoidPage_Load(objectsender,EventArgse) stringkey=Key1; HttpCookiec=newHttpCookie(key,DateTime.Now.ToString

21、(); Response.Cookies.Add(c); HttpCookiecookie=Request.Cookieskey; if(cookie!=null) this.labResult.Text=cookie.Value; Response.Cookies.Remove(key); 這段代碼的運(yùn)行結(jié)果就是【能顯示當(dāng)前時(shí)間】,我就不貼圖了。如果寫成如下形式:protectedvoidPage_Load(objectsender,EventArgse) stringkey=Key1; HttpCookiecookie=Request.Cookieskey; if(cookie!=null

22、) this.labResult.Text=cookie.Value; HttpCookiec=newHttpCookie(key,DateTime.Now.ToString(); Response.Cookies.Add(c); Response.Cookies.Remove(key); 此時(shí)就讀不到Cookie了。這也提示我們:Cookie的讀寫次序可能會(huì)影響我們的某些判斷。HttpRequest還有二個(gè)用于方便獲取HTTP數(shù)據(jù)的屬性Params,Item ,后者是個(gè)默認(rèn)的索引器。這二個(gè)屬性都可以讓我們方便地根據(jù)一個(gè)KEY去【同時(shí)搜索】QueryString、Form、Cookies 或

23、ServerVariables這4個(gè)集合。通常如果請求是用GET方法發(fā)出的,那我們一般是訪問QueryString去獲取用戶的數(shù)據(jù),如果請求是用POST方法提交的,我們一般使用Form去訪問用戶提交的表單數(shù)據(jù)。而使用Params,Item可以讓我們在寫代碼時(shí)不必區(qū)分是GET還是POST。這二個(gè)屬性唯一不同的是:Item是依次訪問這4個(gè)集合,找到就返回結(jié)果,而Params是在訪問時(shí),先將4個(gè)集合的數(shù)據(jù)合并到一個(gè)新集合(集合不存在時(shí)創(chuàng)建),然后再查找指定的結(jié)果。為了更清楚地演示這們的差別,請看以下示例代碼: Item結(jié)果: Params結(jié)果: formaction=method=post publ

24、icpartialclassShowItem:System.Web.UI.Page protectedstringItemValue; protectedstringParamsValue; protectedvoidPage_Load(objectsender,EventArgse) stringallkeys=Request.QueryString.AllKeys; if(allkeys.Length=0) Response.Redirect(ShowItem.aspx?name=abc,true); ItemValue=Requestname; ParamsValue=Request.P

25、aramsname; 頁面在未提交前瀏覽器的顯示:點(diǎn)擊提交按鈕后,瀏覽器的顯示:差別很明顯,我也不多說了。說下我的建議吧:盡量不要使用Params,不光是上面的結(jié)果導(dǎo)致的判斷問題,沒必要多創(chuàng)建一個(gè)集合出來吧,而且更糟糕的是寫Cookie后,也會(huì)更新集合。HttpRequest還有二個(gè)很【低調(diào)】的屬性:InputStream, Filter ,這二位的能量很巨大,卻不經(jīng)常被人用到。HttpResponse也有這二個(gè)對應(yīng)的屬性,本文的后面部分將向您展示它們的強(qiáng)大功能。HttpResponse我們處理HTTP請求的最終目的只有一個(gè):向客戶端返回結(jié)果。而所有需要向客戶端返回的操作都要調(diào)用HttpRes

26、ponse的方法。它提供的功能集中在操作HTTP響應(yīng)部分,如:響應(yīng)流,響應(yīng)頭。我把一些認(rèn)為很重要的成員簡單列舉了一下:/獲取網(wǎng)頁的緩存策略(過期時(shí)間、保密性、變化子句)。 publicHttpCachePolicyCacheget; /獲取或設(shè)置輸出流的HTTPMIME類型。默認(rèn)值為“text/html”。 publicstringContentTypeget;set; /獲取響應(yīng)Cookie集合。 publicHttpCookieCollectionCookiesget; /獲取或設(shè)置一個(gè)包裝篩選器對象,該對象用于在傳輸之前修改HTTP實(shí)體主體。 publicStreamFilterget;

27、set; /啟用到輸出Http內(nèi)容主體的二進(jìn)制輸出。 publicStreamOutputStreamget; /獲取或設(shè)置返回給客戶端的輸出的HTTP狀態(tài)代碼。默認(rèn)值為200(OK)。 publicintStatusCodeget;set; /將HTTP頭添加到輸出流。 publicvoidAppendHeader(stringname,stringvalue); /將當(dāng)前所有緩沖的輸出發(fā)送到客戶端,停止該頁的執(zhí)行,并引發(fā)EndRequest事件。 publicvoidEnd(); /將客戶端重定向到新的URL。指定新的URL并指定當(dāng)前頁的執(zhí)行是否應(yīng)終止。 publicvoidRedirec

28、t(stringurl,boolendResponse); /將指定的文件直接寫入HTTP響應(yīng)輸出流,而不在內(nèi)存中緩沖該文件。 publicvoidTransmitFile(stringfilename); /將System.Object寫入HTTP響應(yīng)流。 publicvoidWrite(objectobj);這些成員都有簡單的解釋,應(yīng)該了解它們。這里請關(guān)注一下屬性StatusCode。我們經(jīng)常用JQuery來實(shí)現(xiàn)Ajax,比如:使用ajax()函數(shù),雖然你可以設(shè)置error回調(diào)函數(shù),但是,極有可能在服務(wù)端即使拋黃頁了,也不會(huì)觸發(fā)這個(gè)回調(diào)函數(shù),除非是設(shè)置了dataType=json,這時(shí)在解

29、析失敗時(shí),才會(huì)觸發(fā)這個(gè)回調(diào)函數(shù),如果是dataType=html,就算是黃頁了,也能【正常顯示】。怎么辦?在服務(wù)端發(fā)生異常不能返回正確結(jié)果時(shí),請?jiān)O(shè)置StatusCode屬性,比如:Response.StatusCode = 500;HttpContext終于輪到大人物出場了。應(yīng)該可以這么說:有了HttpRequest, HttpResponse分別控制了輸入輸出,就應(yīng)該沒有更重要的東西了。但我們用的都是HttpRequest, HttpResponse的實(shí)例,它們在哪里創(chuàng)建的呢,哪里保存有它們最原始的引用呢?答案當(dāng)然是:HttpContext 。沒有老子哪有兒子,就這么個(gè)關(guān)系。更關(guān)鍵的是:這個(gè)

30、老子還很牛,【在任何地方都能找到它】,而且我前面提到另二個(gè)實(shí)力不錯(cuò)的選手(HttpServerUtility和Cache),也都是它的手下。因此,任何事情,找到它就算是有辦法了。你說它是不是最牛。不僅如此,在ASP.NET的世界,還有黑白二派。Module像個(gè)土匪,什么請求都要去“檢查”一下,Handler更像白道上的人物,點(diǎn)名了只做某某事。有趣的是:HttpContext真像個(gè)大人物,黑白道的人物有時(shí)都要找它幫忙。幫什么忙呢?可憐的土匪沒有倉庫,它有東西沒地方存放,只能存放在HttpContext那里,有時(shí)惹得Handler也盯上了它,去HttpContext去拿土匪的戰(zhàn)利品。這位大人物的傳

31、奇故事大致就這樣。我們再來從技術(shù)的角度來觀察它的功能。雖然HttpContext也公開了一些屬性和方法,但我認(rèn)為最重要的還是上面提到的那些對象的引用。這里再補(bǔ)充二個(gè)上面沒提到的實(shí)例屬性:User, ItemsUser屬性保存于當(dāng)前請求的用戶身份信息。如果判斷當(dāng)前請求的用戶是不是已經(jīng)過身份認(rèn)證,可以訪問:Request.IsAuthenticated這個(gè)實(shí)例屬性。前面我在故事中提到:“可憐的土匪沒有倉庫,它有東西沒地方存放,只能存放在HttpContext那里”,其實(shí)這些東西就是保存在Items屬性中。這是個(gè)字典,因此適合以Key/Value的方式來訪問。如果希望在一次請求的過程中保存一些臨時(shí)數(shù)

32、據(jù),那么,這個(gè)屬性是最理想的存放容器了。它會(huì)在下次請求重新創(chuàng)建,因此,不同的請求之間,數(shù)據(jù)不會(huì)被共享。如果希望提供一些靜態(tài)屬性,并且,只希望與一次請求關(guān)聯(lián),那么建議借助HttpContext.Items的實(shí)例屬性來實(shí)現(xiàn)。我曾經(jīng)見過有人用ThreadStaticAttribute來實(shí)現(xiàn)這個(gè)功能,然后在Page.Init事件中去修改那個(gè)字段。哎,哥啊,MSDN上說:【用 ThreadStaticAttribute 標(biāo)記的 static 字段不在線程之間共享。每個(gè)執(zhí)行線程都有單獨(dú)的字段實(shí)例,并且獨(dú)立地設(shè)置及獲取該字段的值。如果在不同的線程中訪問該字段,則該字段將包含不同的值?!?注意了:一個(gè)線程可以

33、執(zhí)行多次請求過程,且Page.Init事件在ASP.NET的管道中屬于較中間的事件啊,要是請求不使用Page呢,您再想想吧。前面我提到HttpContext有種超能力:【在任何地方都能找到它】,是的,HttpContext有個(gè)靜態(tài)屬性Current,你說是不是【在任何地方都能找到它】。千萬別小看這個(gè)屬性,沒有它,HttpContext根本牛不起來。也正是因?yàn)檫@個(gè)屬性,在ASP.NET的世界里,您可以在任何地方訪問Request, Response, Server, Cache,還能在任何地方將一些與請求有關(guān)的臨時(shí)數(shù)據(jù)保存起來,這絕對是個(gè)非常強(qiáng)大的功能。Module的在不同的事件階段,以及與Ha

34、ndler的”溝通“有時(shí)就通過這個(gè)方式來完成。還記得我上篇博客【Session,有沒有必要使用它?】中提到的事情嗎:每個(gè)頁面使用Session的方式是使用Page指令來說明的,但Session是由SessionStateModule來實(shí)現(xiàn)的, SessionStateModule會(huì)處理所有的請求,所以,它不知道當(dāng)前要請求的要如何使用Session,但是,HttpContext提供了一個(gè)屬性Handler讓它們之間有機(jī)會(huì)溝通,才能處理這個(gè)問題。這個(gè)例子反映了Module與Handler溝通的方式,我再來舉個(gè)Module自身溝通的例子,就說UrlRoutingModule吧,它訂閱了二個(gè)事件:pr

35、otectedvirtualvoidInit(HttpApplicationapplication) application.PostResolveRequestCache+=newEventHandler(this.OnApplicationPostResolveRequestCache); application.PostMapRequestHandler+=newEventHandler(this.OnApplicationPostMapRequestHandler); 在OnApplicationPostResolveRequestCache方法中,最終做了以下調(diào)用:publicvir

36、tualvoidPostResolveRequestCache(HttpContextBasecontext) /. RequestDatadata2=newRequestData OriginalPath=context.Request.Path, HttpHandler=httpHandler ; context.Items_requestDataKey=data2; context.RewritePath(/UrlRouting.axd); 再來看看OnApplicationPostMapRequestHandler方法中,最終做了以下調(diào)用:publicvirtualvoidPostMa

37、pRequestHandler(HttpContextBasecontext) RequestDatadata=(RequestData)context.Items_requestDataKey; if(data!=null) context.RewritePath(data.OriginalPath); context.Handler=data.HttpHandler; 看到了嗎,HttpContext.Items為Module在不同的事件中保存了臨時(shí)數(shù)據(jù),而且很方便。強(qiáng)大的背后也有麻煩事前面我們看到了HttpContext的強(qiáng)大,而且還提供HttpContext.Current這個(gè)靜態(tài)屬性

38、。這樣一來,的確是【在任何地方都能找到它】。想想我們能做什么?我們可以在任何一個(gè)類庫中都可以訪問QueryString, Form,夠靈活吧。我們還可以在任何地方(比如BLL中)調(diào)用Response.Redirect()讓請求重定向,是不是很強(qiáng)大?不過,有個(gè)很現(xiàn)實(shí)的問題擺在面前:到處訪問這些對象會(huì)讓代碼很難測試。原因很簡單:在測試時(shí),這些對象沒法正常工作,因?yàn)镠ttpRuntime很多幕后的事情還沒做,沒有運(yùn)行它們的環(huán)境。是不是很掃興?沒辦法,現(xiàn)在的測試水平很難駕馭這些功能強(qiáng)大的對象。很多人都說WebForms框架搞得代碼沒法測試,通常也是的確如此。我看到很多人在頁面的CodeFile中寫了一

39、大堆的控件操作代碼,還混有很多調(diào)用業(yè)務(wù)邏輯的代碼,甚至在類庫項(xiàng)目中還中訪問QueryString, Cookie。再加上諸如ViewState, Session這類【有狀態(tài)】的東西大量使用,這樣的代碼是很難測試。換個(gè)視角,看看MVC框架為什么說可測試性會(huì)好很多,理由很簡單,你很少會(huì)需要使用HttpRequest, HttpRespons,從Controller開始,您需要的數(shù)據(jù)已經(jīng)給您準(zhǔn)備好了,直接用就可以了。但MVC框架并不能保證寫的代碼就一定能方便的測試,比如:您繼續(xù)使用HttpContext.Current.XXXXX而不使用那些HttpXxxxxBase對象。一般說來,很多人會(huì)采用三層

40、或者多層的方式來組織他們的項(xiàng)目代碼。此時(shí),如果您希望您的核心代碼是可測試的,并且確實(shí)需要使用這些對象,那么應(yīng)該盡量集中使用這些強(qiáng)大的對象,應(yīng)該在最靠近UI層的地方去訪問它們。可以把調(diào)用業(yè)務(wù)邏輯的代碼再提取到一個(gè)單獨(dú)的層中,比如就叫“服務(wù)層”吧,由服務(wù)層去調(diào)用下面的BLL(假設(shè)BLL的API的粒度較小),服務(wù)層由表示層調(diào)用,調(diào)用服務(wù)層的參數(shù)由表示層從HttpRequest中取得。需要操作Response對象時(shí),比如:重定向這類操作,則應(yīng)該在表示層中完成。記住:只有表示層才能訪問前面提到的對象,而且要讓表示層盡量簡單,簡單到不需要測試,真正需要測試的代碼(與業(yè)務(wù)邏輯有關(guān))放在表示層以下。如此設(shè)計(jì),

41、您的表示層將非常簡單,以至于不用測試(MVC框架中的View也能包含代碼,但也沒法測試,是一樣的道理)。甚至,服務(wù)層還可以單獨(dú)部署。如果您的項(xiàng)目真的采用分層的設(shè)計(jì),那么,就應(yīng)該可以讓界面與業(yè)務(wù)處理分離。比如您可以這樣設(shè)計(jì):1. 表示層只處理輸入輸出的事情,它應(yīng)該僅負(fù)責(zé)與用戶的交互處理,建議這層代碼簡單到可以忽略測試。2. 處理請求由UI層以下的邏輯層來完成,它負(fù)責(zé)請求的具體實(shí)現(xiàn)過程,它的方法參數(shù)來自于表示層。為了檢驗(yàn)?zāi)姆謱釉O(shè)計(jì)是否符合這個(gè)原則,有個(gè)很簡單的方法:寫個(gè)console小程序模擬UI層調(diào)用下層方法,能正常運(yùn)行,就說明您的分層是正確的,否則,建議改進(jìn)它們。換一種方式使用ASP.NET

42、框架前面我提到HttpRequest有個(gè)InputStream屬性, HttpResponse有一個(gè)OutputStream屬性,它們對應(yīng)的是輸入輸出流。直接使用它們,我們可以非常簡單地提供一些服務(wù)功能,比如:我希望直接使用JSON格式來請求和應(yīng)答。如果采用這種方案來設(shè)計(jì),我們只需要定義好輸入輸出的數(shù)據(jù)結(jié)構(gòu),并使用這們來傳輸數(shù)據(jù)就好了。當(dāng)然了,也有其它的方法能實(shí)現(xiàn),但它們不是本文的主題,我也比較喜歡這種簡單又直觀地方式來解決某些問題。2007年我做過一個(gè)短信的接口,人家就提供幾個(gè)URL做為服務(wù)的地址,調(diào)用參數(shù)以及返回值就直接通過HTTP請求一起傳遞。2009年做過一個(gè)項(xiàng)目是調(diào)用Experian

43、 Precise ID服務(wù)(Java寫的),那個(gè)服務(wù)也直接使用HTTP協(xié)議,數(shù)據(jù)格式采用XML,輸出輸入的數(shù)據(jù)結(jié)構(gòu)由他們定義的自定義類型。2010年,我做過一個(gè)數(shù)據(jù)訪問層服務(wù),與C+的客戶端通信,采用ASP.NET加JSON數(shù)據(jù)格式的方式。基本上這三個(gè)項(xiàng)目都有一個(gè)共同點(diǎn):直接使用HTTP協(xié)議,數(shù)據(jù)結(jié)構(gòu)有著明確的定義格式,直接隨HTTP一起傳遞。就這么簡單,卻非常有用,而且適用性很廣,基本上什么語言都能很好地相互調(diào)用。下面我以一個(gè)簡單的示例演示這二個(gè)屬性的強(qiáng)大之處。在示例中,服務(wù)端要求數(shù)據(jù)的輸入輸出采用JSON格式,服務(wù)的功能是一個(gè)訂單查詢功能,輸入輸出的類型定義如下:/查詢訂單的輸入?yún)?shù) pu

44、blicsealedclassQueryOrderCondition publicint?OrderId; publicint?CustomerId; publicDateTimeStartDate; publicDateTimeEndDate; /查詢訂單的輸出參數(shù)類型 publicsealedclassOrder publicintOrderIDget;set; publicintCustomerIDget;set; publicstringCustomerNameget;set; publicDateTimeOrderDateget;set; publicdoubleSumMoneyge

45、t;set; publicstringCommentget;set; publicboolFinishedget;set; publicListDetailget;set; publicsealedclassOrderDetail publicintOrderIDget;set; publicintQuantityget;set; publicintProductIDget;set; publicstringProductNameget;set; publicstringUnitget;set; publicdoubleUnitPriceget;set; 服務(wù)端的實(shí)現(xiàn):創(chuàng)建一個(gè)QueryOrd

46、erService.ashx,具體實(shí)現(xiàn)代碼如下:publicclassQueryOrderService:IHttpHandler publicvoidProcessRequest(HttpContextcontext) context.Response.ContentType=application/json; stringinput=null; JavaScriptSerializerjss=newJavaScriptSerializer(); using(StreamReadersr=newStreamReader(context.Request.InputStream) input=s

47、r.ReadToEnd(); QueryOrderConditionquery=jss.Deserialize(input); /模擬查詢過程,這里就直接返回一個(gè)列表。 Listlist=newList(); for(inti=0;i10;i+) list.Add(DataFactory.CreateRandomOrder(); stringjson=jss.Serialize(list); context.Response.Write(json); 代碼很簡單,經(jīng)過了以下幾個(gè)步驟:1. 從Request.InputStream中讀取客戶端發(fā)送過來的JSON字符串,2. 反序列化成需要的輸入?yún)?/p>

48、數(shù),3. 執(zhí)行查詢訂單的操作,生成結(jié)果數(shù)據(jù),4. 將結(jié)果做JSON序列化,轉(zhuǎn)成字符串,5. 寫入到響應(yīng)流。很簡單吧,我可以把它看作是一個(gè)服務(wù)吧,但它沒有其它服務(wù)框架的種種約束,而且相當(dāng)靈活,比如我可以讓服務(wù)采用GZIP的方式來壓縮傳輸數(shù)據(jù):publicvoidProcessRequest(HttpContextcontext) context.Response.ContentType=application/json; stringinput=null; JavaScriptSerializerjss=newJavaScriptSerializer(); using(GZipStreamgzi

49、p=newGZipStream(context.Request.InputStream,CompressionMode.Decompress) using(StreamReadersr=newStreamReader(gzip) input=sr.ReadToEnd(); QueryOrderConditionquery=jss.Deserialize(input); /模擬查詢過程,這里就直接返回一個(gè)列表。 Listlist=newList(); for(inti=0;i10;i+) list.Add(DataFactory.CreateRandomOrder(); stringjson=j

50、ss.Serialize(list); using(GZipStreamgzip=newGZipStream(context.Response.OutputStream,CompressionMode.Compress) using(StreamWritersw=newStreamWriter(gzip) context.Response.AppendHeader(Content-Encoding,gzip); sw.Write(json); 修改也很直觀,在輸入輸出的地方,加上Gzip的操作就可以了。如果您想加密傳輸內(nèi)容,也可以在讀寫之間做相應(yīng)的處理,或者,想換個(gè)序列化方式,也簡單,我想您應(yīng)該懂的??傊绾巫x寫數(shù)據(jù),全由您來決定。喜歡怎樣處理就怎樣處理,這就是自由。不僅如此,我還可以讓服務(wù)端判斷客戶端

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論