av激情亚洲男人的天堂国语,日韩欧美精品一中文字幕,无码av一区二区三区无码,国产又色又爽又刺激的a片,国产又色又爽又刺激的a片

淺談ASP.NET核心對(duì)象

  想當(dāng)初在只使用WebForms框架并以服務(wù)端為中心的開(kāi)發(fā)模式時(shí),發(fā)現(xiàn)ASP.NET好復(fù)雜。一大堆服務(wù)端控件,各有各的使用方法,有些控件的事件也很重要,必須在合適地時(shí)機(jī)去響應(yīng),還真有些復(fù)雜。后來(lái)逐漸發(fā)現(xiàn)這些復(fù)雜的根源其實(shí)就是服務(wù)器控件相關(guān)的抽象邏輯。隨著Ajax越用越多,可能有些人也做過(guò)這些事情:【新建一個(gè)ashx文件,讀取一些用戶的輸入數(shù)據(jù),F(xiàn)orm, QueryString,然后調(diào)用業(yè)務(wù)邏輯代碼,將處理后的結(jié)果序列化成JSON字符串再發(fā)給客戶端】,這樣也能完成一次請(qǐng)求。不知大家有沒(méi)有做過(guò)這類事情,反正我是做過(guò)的。慢慢地,我也嫌煩了,這些事情中除了調(diào)用業(yè)務(wù)邏輯部分,都是些體力活嘛。于是想,寫(xiě)點(diǎn)代碼把這些事情交給它們?nèi)プ霭?,我只處理與請(qǐng)求有關(guān)的數(shù)據(jù)處理就好了。終于,我寫(xiě)了個(gè)簡(jiǎn)陋的框架,并自稱為【我的Ajax服務(wù)端框架】以及【我的MVC框架】。寫(xiě)完這些東西后,發(fā)現(xiàn)ASP.NET的東西變少了,但是仍可以實(shí)現(xiàn)很多功能。

  其實(shí),我們可以從另一角度來(lái)看ASP.NET,它就是一個(gè)底層框架平臺(tái),它負(fù)責(zé)接收HTTP請(qǐng)求(從IIS傳入),將請(qǐng)求分配給一個(gè)線程,再把請(qǐng)求放到它的處理管道中,由一些其它的【管道事件訂閱者】來(lái)處理它們,最后將處理結(jié)果返回給客戶端。而WebForms或者M(jìn)VC框架,都屬于ASP.NET平臺(tái)上的【管道事件訂閱者】而已,Web Service也是哦。如果你不想受限于WebForms或者M(jìn)VC框架,或者您還想用ASP.NET做點(diǎn)其它的事情,比如:自己的服務(wù)框架,就像WebService那樣。但希望用其它更簡(jiǎn)單的序列化方式來(lái)減少網(wǎng)絡(luò)流量,或者還有加密要求。那么了解ASP.NET提供了哪些功能就很有必要了。

  本文將站在ASP.NET平臺(tái)的角度,來(lái)看看ASP.NET的一些基礎(chǔ)功能。雖然不會(huì)涉及任何其它上層框架,但所講述的內(nèi)容其實(shí)是適合其它上層框架的。

  前面我說(shuō)到:ASP.NET負(fù)責(zé)接收請(qǐng)求,并將請(qǐng)求分配給一個(gè)線程來(lái)執(zhí)行。最終執(zhí)行什么呢?當(dāng)然就是我們的處理邏輯。但我們?cè)谔幚頃r(shí),用戶輸入的數(shù)據(jù)又是從哪里來(lái)的呢?只能是HTTP請(qǐng)求。但它又可分為二個(gè)部分:請(qǐng)求頭和請(qǐng)求體。在ASP.NET中,我們并不需要去分析請(qǐng)求頭和請(qǐng)求體,比如:我們可以直接訪問(wèn)QueryString,F(xiàn)orm就可以得到用戶傳過(guò)來(lái)的數(shù)據(jù)了,然而QueryString其實(shí)是放在請(qǐng)求頭上,在請(qǐng)求頭上的還有Cookie,F(xiàn)orm以及PostFile則放在請(qǐng)求體中。如果對(duì)這些內(nèi)容不清楚的可以參考我的博客:【細(xì)說(shuō)Cookie】和【細(xì)說(shuō) Form (表單)】。在我這二篇博客中,您應(yīng)該可以看出:要是讓您從請(qǐng)求頭請(qǐng)求體中讀取這些數(shù)據(jù),還是很麻煩的。幸好,ASP.NET做為底層平臺(tái),在每次處理請(qǐng)求時(shí),都將這些數(shù)據(jù)轉(zhuǎn)成方便我們處理的對(duì)象了。今天我將只談這些基礎(chǔ)對(duì)象以及它們可以實(shí)現(xiàn)的功能。

  在我的眼里,ASP.NET有三大核心對(duì)象:HttpContext, HttpRequest, HttpResponse。

  除此之外,還有二個(gè)對(duì)象雖然稱不上核心,但仍然比較重要:HttpRuntime,HttpServerUtility

  事實(shí)上,這些類的實(shí)例在其它的一些類型中也經(jīng)常被引用到,從出現(xiàn)的頻率也可以看出它們的重要性。

  中國(guó)人喜歡把較重要的東西放在最后,做為壓軸出場(chǎng)。今天我也將按照這個(gè)風(fēng)俗習(xí)慣做為這些對(duì)象的出場(chǎng)順序來(lái)分別說(shuō)說(shuō)它們有哪些【重要的功能】。

  HttpRuntime

  第一個(gè)出場(chǎng)的是HttpRuntime,其實(shí)這個(gè)對(duì)象算是整個(gè)ASP.NET平臺(tái)最核心的對(duì)象,從名字可以看出它的份量。但它包含的很多方法都不是public類型的,它在整個(gè)請(qǐng)求的處理過(guò)程中,做了許多默默無(wú)聞但非常重要的工作。反而公開(kāi)的東西并不多,因此需要我們掌握的東西也較少。不能讓它做為壓軸出場(chǎng)就讓它第一個(gè)出場(chǎng)吧。這就是我的想法。

  HttpRuntime公開(kāi)了一個(gè)方法靜態(tài) UnloadAppDomain() ,這個(gè)方法可以讓我們用代碼重新啟動(dòng)網(wǎng)站。通常用于用戶通過(guò)程序界面修改了一個(gè)比較重要的參數(shù),這時(shí)需要重啟程序了。

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

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

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

  可能還有人會(huì)擔(dān)心往Cache里放入太多的東西會(huì)不會(huì)影響性能,因此有人還想到控制緩存數(shù)量的辦法。我只想說(shuō):緩存容器決定一個(gè)對(duì)象的保存位置是使用Hash算法的,并不會(huì)因?yàn)榫彺骓?xiàng)變多而影響性能,更有趣的是ASP.NET的Cache的容器還并非只有一個(gè),它能隨著CPU的數(shù)量而調(diào)整,看這個(gè)架式,應(yīng)該在設(shè)計(jì)Cache時(shí)還想到了高并發(fā)訪問(wèn)的性能問(wèn)題。如果這時(shí)你還在統(tǒng)計(jì)緩存數(shù)量并手工釋放某些緩存項(xiàng),我只能說(shuō)您在寫(xiě)損害性能的代碼。

  HttpServerUtility , HttpUtility

  不要覺(jué)得奇怪,這次我一下子請(qǐng)了二個(gè)對(duì)象出場(chǎng)了。由于HttpServerUtility的實(shí)例通常以Server的屬性公開(kāi),但它的提供一些Encode, Decode方法其實(shí)調(diào)用的是HttpUtility類的靜態(tài)方法。所以我就把它們倆一起請(qǐng)出來(lái)了。

  HttpUtility公開(kāi)了一些靜態(tài)方法,如:

  HtmlEncode(),應(yīng)該是使用頻率比較高的方法,用于防止注入攻擊,它負(fù)責(zé)安全地生成一段HTML代碼。

  有時(shí)我們還需要生成一個(gè)URL,那么UrlEncode()方法就能派上用場(chǎng)了,因?yàn)閁RL中并不能包含所有字符,所以要做相應(yīng)的編碼。

  HttpUtility還有一個(gè)方法HtmlAttributeEncode(),它也是用于防止注入攻擊,安全地輸出一個(gè)HTML屬性。

  在.net4中,HttpUtility還提供了另一個(gè)方法:JavaScriptStringEncode(),也是為了防止注入攻擊,安全地在服務(wù)端輸出一段JS代碼。

  HttpUtility還公開(kāi)了一些靜態(tài)方法,如:

  HtmlDecode(), UrlDecode(),通常來(lái)說(shuō),我們并不需要使用它們。尤其是UrlDecode ,除非您要自己的框架,一般來(lái)說(shuō),在我們?cè)L問(wèn)QueryString, Form時(shí),已經(jīng)做過(guò)UrlDecode了,您就不用再去調(diào)用了。

  HttpServerUtility除了公開(kāi)了比較常用的Encode, Decode方法外,還公開(kāi)了一個(gè)非常有用的方法:Execute(),是的,它非常有用,尤其是您需要在服務(wù)端獲取一個(gè)頁(yè)面或者用戶控件的HTML輸出時(shí)。如果您對(duì)這個(gè)功能有興趣可以參考我的博客:【我的Ajax服務(wù)端框架 - (4) JS直接請(qǐng)求ascx用戶控件】

  HttpRequest

  現(xiàn)在總算輪到第一個(gè)核心對(duì)象出場(chǎng)了。MSDN給它作了一個(gè)簡(jiǎn)短的解釋:“使 ASP.NET 能夠讀取客戶端在 Web 請(qǐng)求期間發(fā)送的 HTTP 值?!?/p>

  這個(gè)解釋還算是到位的。HttpRequest的實(shí)例包含了所有來(lái)自客戶端的所有數(shù)據(jù),我們可以把這些數(shù)據(jù)看成是輸入數(shù)據(jù), Handler以及Module就相當(dāng)于是處理過(guò)程,HttpResponse就是輸出了。

  在HttpRequest包含的所有輸入數(shù)據(jù)中,有我們經(jīng)常使用的QueryString, Form, Cookie,它還允許我們?cè)L問(wèn)一些HTTP請(qǐng)求頭、瀏覽器的相關(guān)信息、請(qǐng)求映射的相關(guān)文件路徑、URL詳細(xì)信息、請(qǐng)求的方法、請(qǐng)求是否已經(jīng)過(guò)身份驗(yàn)證,是否為SSL等等。

  HttpRequest的公開(kāi)屬性絕大部分都是比較重要的,這里就簡(jiǎn)單地列舉一下吧。

 
 
 
 
  1.   // 獲取服務(wù)器上 ASP.NET 應(yīng)用程序的虛擬應(yīng)用程序根路徑。  
  2.   public string ApplicationPath { get;}  
  3.   // 獲取應(yīng)用程序根的虛擬路徑,并通過(guò)對(duì)應(yīng)用程序根使用波形符 (~) 表示法(例如,以“~/page.aspx”的形式)使該路徑成為相對(duì)路徑。  
  4.   public string AppRelativeCurrentExecutionFilePath { get;}  
  5.   // 獲取或設(shè)置有關(guān)正在請(qǐng)求的客戶端的瀏覽器功能的信息。  
  6.   public HttpBrowserCapabilities Browser { get;set;}  
  7.   // 獲取客戶端發(fā)送的 cookie 的集合。  
  8.   public HttpCookieCollection Cookies { get;}  
  9.   // 獲取當(dāng)前請(qǐng)求的虛擬路徑。  
  10.   public string FilePath { get;}  
  11.   // 獲取采用多部分 MIME 格式的由客戶端上載的文件的集合。  
  12.  public HttpFileCollection Files { get;}  
  13.   // 獲取或設(shè)置在讀取當(dāng)前輸入流時(shí)要使用的篩選器。  
  14.   public Stream Filter { get;set;}  
  15.   // 獲取窗體變量集合。  
  16.   public NameValueCollection Form { get;}  
  17.   // 獲取 HTTP 頭集合。  
  18.   public NameValueCollection Headers { get;}  
  19.   // 獲取客戶端使用的 HTTP 數(shù)據(jù)傳輸方法(如 GET、POST 或 HEAD)。  
  20.   public string HttpMethod { get;}  
  21.   // 獲取傳入的 HTTP 實(shí)體主體的內(nèi)容。  
  22.   public Stream InputStream { get;}  
  23.   // 獲取一個(gè)值,該值指示是否驗(yàn)證了請(qǐng)求。  
  24.   public bool IsAuthenticated { get;}  
  25.   // 獲取當(dāng)前請(qǐng)求的虛擬路徑。  
  26.   public string Path { get;}  
  27.   // 獲取 HTTP 查詢字符串變量集合。  
  28.  public NameValueCollection QueryString { get;}  
  29.   // 獲取當(dāng)前請(qǐng)求的原始 URL。  
  30.   public string RawUrl { get;}  
  31.   // 獲取有關(guān)當(dāng)前請(qǐng)求的 URL 的信息。  
  32.   public Uri Url { get;}  
  33.   // 從 QueryString、Form、Cookies 或 ServerVariables 集合中獲取指定的對(duì)象。  
  34.   public string this[string key] { get;}  
  35.   // 將指定的虛擬路徑映射到物理路徑。  
  36.   // 參數(shù): virtualPath: 當(dāng)前請(qǐng)求的虛擬路徑(絕對(duì)路徑或相對(duì)路徑)。  
  37.   // 返回結(jié)果: 由 virtualPath 指定的服務(wù)器物理路徑。  
  38.   public string MapPath(string virtualPath); 

  下面我來(lái)說(shuō)說(shuō)一些不被人注意的細(xì)節(jié)。

  HttpRequest的QueryString, Form屬性的類型都是NameValueCollection,它個(gè)集合類型有一個(gè)特點(diǎn):允許在一個(gè)鍵下存儲(chǔ)多個(gè)字符串值。

  以下代碼演示了這個(gè)特殊的現(xiàn)象:

 
 
 
 
  1.   protected void Page_Load(object sender, EventArgs e)  
  2.   {  
  3.   string[] allkeys = Request.QueryString.AllKeys;  
  4.   if( allkeys.Length == 0 )  
  5.   Response.Redirect(  
  6.   Request.RawUrl + "?aa=1&bb=2&cc=3&aa=" + HttpUtility.UrlEncode("5,6,7"), true);  
  7.   StringBuilder sb = new StringBuilder();  
  8.   foreach( string key in allkeys )  
  9.   sb.AppendFormat("{0} = {1}",  
  10.   HttpUtility.HtmlEncode(key), HttpUtility.HtmlEncode(Request.QueryString[key]));  
  11.   this.labResult.Text = sb.ToString();  
  12.   } 

  頁(yè)面最終顯示結(jié)果如下(注意鍵值為aa的結(jié)果):

說(shuō)明:

  1. HttpUtility.ParseQueryString(string)這個(gè)靜態(tài)方法能幫助我們解析一個(gè)URL字符串,返回的結(jié)果也是NameValueCollection類型。

  2. NameValueCollection是一個(gè)不區(qū)分大小寫(xiě)的集合。

  HttpRequest有一個(gè)Cookies屬性,MSDN給它的解釋是:“獲取客戶端發(fā)送的 Cookie 的集合?!?,這次MSDN的解釋就不完全準(zhǔn)確了。

  請(qǐng)看如下代碼:

 
 
 
 
  1.   protected void Page_Load(object sender, EventArgs e)  
  2.   {  
  3.   string key = "Key1";  
  4.   HttpCookie c = new HttpCookie(key, DateTime.Now.ToString());  
  5.   Response.Cookies.Add(c);  
  6.   HttpCookie cookie = Request.Cookies[key];  
  7.   if( cookie != null )  
  8.   this.labResult.Text = cookie.Value;  
  9.   Response.Cookies.Remove(key);  
  10.   } 

  這段代碼的運(yùn)行結(jié)果就是【能顯示當(dāng)前時(shí)間】,我就不貼圖了。

  如果寫(xiě)成如下形式:

 
 
 
 
  1.   protected void Page_Load(object sender, EventArgs e)  
  2.   {  
  3.   string key = "Key1";  
  4.   HttpCookie cookie = Request.Cookies[key];  
  5.   if( cookie != null )  
  6.   this.labResult.Text = cookie.Value;  
  7.   HttpCookie c = new HttpCookie(key, DateTime.Now.ToString());  
  8.   Response.Cookies.Add(c);  
  9.   Response.Cookies.Remove(key);  
  10.   } 

  此時(shí)就讀不到Cookie了。這也提示我們:Cookie的讀寫(xiě)次序可能會(huì)影響我們的某些判斷。

  HttpRequest還有二個(gè)用于方便獲取HTTP數(shù)據(jù)的屬性Params,Item ,后者是個(gè)默認(rèn)的索引器。

  這二個(gè)屬性都可以讓我們方便地根據(jù)一個(gè)KEY去【同時(shí)搜索】QueryString、Form、Cookies 或 ServerVariables這4個(gè)集合。通常如果請(qǐng)求是用GET方法發(fā)出的,那我們一般是訪問(wèn)QueryString去獲取用戶的數(shù)據(jù),如果請(qǐng)求是用POST方法提交的,我們一般使用Form去訪問(wèn)用戶提交的表單數(shù)據(jù)。而使用Params,Item可以讓我們?cè)趯?xiě)代碼時(shí)不必區(qū)分是GET還是POST。這二個(gè)屬性唯一不同的是:Item是依次訪問(wèn)這4個(gè)集合,找到就返回結(jié)果,而Params是在訪問(wèn)時(shí),先將4個(gè)集合的數(shù)據(jù)合并到一個(gè)新集合(集合不存在時(shí)創(chuàng)建),然后再查找指定的結(jié)果。

  為了更清楚地演示這們的差別,請(qǐng)看以下示例代碼:

 
 
 
 
  1.     
  2.   

    Item結(jié)果:<%= this.ItemValue %>

     
  3.   

    Params結(jié)果:<%= this.ParamsValue %>

     
  4.     
  5.   " method="post">  
  6.     
  7.     
  8.     
  9.     
  10.   public partial class ShowItem : System.Web.UI.Page  
  11.   {  
  12.   protected string ItemValue;  
  13.   protected string ParamsValue;  
  14.   protected void Page_Load(object sender, EventArgs e)  
  15.   {  
  16.   string[] allkeys = Request.QueryString.AllKeys;  
  17.   if( allkeys.Length == 0 )  
  18.   Response.Redirect("ShowItem.aspx?name=abc", true);  
  19.   ItemValue = Request["name"];  
  20.   ParamsValue = Request.Params["name"];  
  21.   }  
  22.   } 

  頁(yè)面在未提交前瀏覽器的顯示:

點(diǎn)擊提交按鈕后,瀏覽器的顯示:

  差別很明顯,我也不多說(shuō)了。說(shuō)下我的建議吧:盡量不要使用Params,不光是上面的結(jié)果導(dǎo)致的判斷問(wèn)題,沒(méi)必要多創(chuàng)建一個(gè)集合出來(lái)吧,而且更糟糕的是寫(xiě)Cookie后,也會(huì)更新集合。

  HttpRequest還有二個(gè)很【低調(diào)】的屬性:InputStream, Filter ,這二位的能量很巨大,卻不經(jīng)常被人用到。

  HttpResponse也有這二個(gè)對(duì)應(yīng)的屬性,本文的后面部分將向您展示它們的強(qiáng)大功能。

  HttpResponse

  我們處理HTTP請(qǐng)求的最終目的只有一個(gè):向客戶端返回結(jié)果。而所有需要向客戶端返回的操作都要調(diào)用HttpResponse的方法。它提供的功能集中在操作HTTP響應(yīng)部分,如:響應(yīng)流,響應(yīng)頭。

  我把一些認(rèn)為很重要的成員簡(jiǎn)單列舉了一下:

 
 
 
 
  1.   // 獲取網(wǎng)頁(yè)的緩存策略(過(guò)期時(shí)間、保密性、變化子句)。  
  2.   public HttpCachePolicy Cache { get;}  
  3.   // 獲取或設(shè)置輸出流的 HTTP MIME 類型。默認(rèn)值為“text/html”。  
  4.   public string ContentType { get;set;}  
  5.   // 獲取響應(yīng) Cookie 集合。  
  6.   public HttpCookieCollection Cookies { get;}  
  7.   // 獲取或設(shè)置一個(gè)包裝篩選器對(duì)象,該對(duì)象用于在傳輸之前修改 HTTP 實(shí)體主體。  
  8.   public Stream Filter { get;set;}  
  9.   // 啟用到輸出 Http 內(nèi)容主體的二進(jìn)制輸出。  
  10.   public Stream OutputStream { get;}  
  11.   // 獲取或設(shè)置返回給客戶端的輸出的 HTTP 狀態(tài)代碼。默認(rèn)值為 200 (OK)。  
  12.   public int StatusCode { get;set;}  
  13.   // 將 HTTP 頭添加到輸出流。  
  14.   public void AppendHeader(string name, string value);  
  15.   // 將當(dāng)前所有緩沖的輸出發(fā)送到客戶端,停止該頁(yè)的執(zhí)行,并引發(fā)EndRequest事件。  
  16.   public void End();  
  17.   // 將客戶端重定向到新的 URL。指定新的 URL 并指定當(dāng)前頁(yè)的執(zhí)行是否應(yīng)終止。  
  18.   public void Redirect(string url, bool endResponse);  
  19.   // 將指定的文件直接寫(xiě)入 HTTP 響應(yīng)輸出流,而不在內(nèi)存中緩沖該文件。  
  20.   public void TransmitFile(string filename);  
  21.   // 將 System.Object 寫(xiě)入 HTTP 響應(yīng)流。  
  22.   public void Write(object obj); 

  這些成員都有簡(jiǎn)單的解釋,應(yīng)該了解它們。

  這里請(qǐng)關(guān)注一下屬性StatusCode。我們經(jīng)常用JQuery來(lái)實(shí)現(xiàn)Ajax,比如:使用ajax()函數(shù),雖然你可以設(shè)置error回調(diào)函數(shù),但是,極有可能在服務(wù)端即使拋黃頁(yè)了,也不會(huì)觸發(fā)這個(gè)回調(diào)函數(shù),除非是設(shè)置了dataType="json",這時(shí)在解析失敗時(shí),才會(huì)觸發(fā)這個(gè)回調(diào)函數(shù),如果是dataType="html",就算是黃頁(yè)了,也能【正常顯示】。

  怎么辦?在服務(wù)端發(fā)生異常不能返回正確結(jié)果時(shí),請(qǐng)?jiān)O(shè)置StatusCode屬性,比如:Response.StatusCode = 500;

  HttpContext

  終于輪到大人物出場(chǎng)了。

  應(yīng)該可以這么說(shuō):有了HttpRequest, HttpResponse分別控制了輸入輸出,就應(yīng)該沒(méi)有更重要的東西了。但我們用的都是HttpRequest, HttpResponse的實(shí)例,它們?cè)谀睦飫?chuàng)建的呢,哪里保存有它們最原始的引用呢?答案當(dāng)然是:HttpContext 。沒(méi)有老子哪有兒子,就這么個(gè)關(guān)系。更關(guān)鍵的是:這個(gè)老子還很牛,【在任何地方都能找到它】,而且我前面提到另二個(gè)實(shí)力不錯(cuò)的選手(HttpServerUtility和Cache),也都是它的手下。因此,任何事情,找到它就算是有辦法了。你說(shuō)它是不是最牛。

  不僅如此,在ASP.NET的世界,還有黑白二派。Module像個(gè)土匪,什么請(qǐng)求都要去“檢查”一下,Handler更像白道上的人物,點(diǎn)名了只做某某事。有趣的是:HttpContext真像個(gè)大人物,黑白道的人物有時(shí)都要找它幫忙。幫什么忙呢?可憐的土匪沒(méi)有倉(cāng)庫(kù),它有東西沒(méi)地方存放,只能存放在HttpContext那里,有時(shí)惹得Handler也盯上了它,去HttpContext去拿土匪的戰(zhàn)利品。

  這位大人物的傳奇故事大致就這樣。我們?cè)賮?lái)從技術(shù)的角度來(lái)觀察它的功能。

  雖然HttpContext也公開(kāi)了一些屬性和方法,但我認(rèn)為最重要的還是上面提到的那些對(duì)象的引用。

  這里再補(bǔ)充二個(gè)上面沒(méi)提到的實(shí)例屬性:User, Items

  User屬性保存于當(dāng)前請(qǐng)求的用戶身份信息。如果判斷當(dāng)前請(qǐng)求的用戶是不是已經(jīng)過(guò)身份認(rèn)證,可以訪問(wèn):Request.IsAuthenticated這個(gè)實(shí)例屬性。

  前面我在故事中提到:“可憐的土匪沒(méi)有倉(cāng)庫(kù),它有東西沒(méi)地方存放,只能存放在HttpContext那里”,其實(shí)這些東西就是保存在Items屬性中。這是個(gè)字典,因此適合以Key/Value的方式來(lái)訪問(wèn)。如果希望在一次請(qǐng)求的過(guò)程中保存一些臨時(shí)數(shù)據(jù),那么,這個(gè)屬性是最理想的存放容器了。它會(huì)在下次請(qǐng)求重新創(chuàng)建,因此,不同的請(qǐng)求之間,數(shù)據(jù)不會(huì)被共享。

  如果希望提供一些靜態(tài)屬性,并且,只希望與一次請(qǐng)求關(guān)聯(lián),那么建議借助HttpContext.Items的實(shí)例屬性來(lái)實(shí)現(xiàn)。

  我曾經(jīng)見(jiàn)過(guò)有人用ThreadStaticAttribute來(lái)實(shí)現(xiàn)這個(gè)功能,然后在Page.Init事件中去修改那個(gè)字段。

  哎,哥啊,MSDN上說(shuō):【用 ThreadStaticAttribute 標(biāo)記的 static 字段不在線程之間共享。每個(gè)執(zhí)行線程都有單獨(dú)的字段實(shí)例,并且獨(dú)立地設(shè)置及獲取該字段的值。如果在不同的線程中訪問(wèn)該字段,則該字段將包含不同的值?!?注意了:一個(gè)線程可以執(zhí)行多次請(qǐng)求過(guò)程,且Page.Init事件在ASP.NET的管道中屬于較中間的事件啊,要是請(qǐng)求不使用Page呢,您再想想吧。

  前面我提到HttpContext有種超能力:【在任何地方都能找到它】,是的,HttpContext有個(gè)靜態(tài)屬性Current,你說(shuō)是不是【在任何地方都能找到它】。千萬(wàn)別小看這個(gè)屬性,沒(méi)有它,HttpContext根本牛不起來(lái)。

  也正是因?yàn)檫@個(gè)屬性,在ASP.NET的世界里,您可以在任何地方訪問(wèn)Request, Response, Server, Cache,還能在任何地方將一些與請(qǐng)求有關(guān)的臨時(shí)數(shù)據(jù)保存起來(lái),這絕對(duì)是個(gè)非常強(qiáng)大的功能。Module的在不同的事件階段,以及與Handler的”溝通“有時(shí)就通過(guò)這個(gè)方式來(lái)完成。

  還記得我上篇博客【Session,有沒(méi)有必要使用它?】中提到的事情嗎:每個(gè)頁(yè)面使用Session的方式是使用Page指令來(lái)說(shuō)明的,但Session是由SessionStateModule來(lái)實(shí)現(xiàn)的, SessionStateModule會(huì)處理所有的請(qǐng)求,所以,它不知道當(dāng)前要請(qǐng)求的要如何使用Session,但是,HttpContext提供了一個(gè)屬性Handler讓它們之間有機(jī)會(huì)溝通,才能處理這個(gè)問(wèn)題。

  這個(gè)例子反映了Module與Handler溝通的方式,我再來(lái)舉個(gè)Module自身溝通的例子,就說(shuō)UrlRoutingModule吧,它訂閱了二個(gè)事件:

 
 
 
 
  1.   protected virtual void Init(HttpApplication application)  
  2.   {  
  3.   application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);  
  4.   application.PostMapRequestHandler += new EventHandler(this.OnApplicationPostMapRequestHandler);  
  5.   } 

  在OnApplicationPostResolveRequestCache方法中,最終做了以下調(diào)用:

 
 
 
 
  1.   public virtual void PostResolveRequestCache(HttpContextBase context)  
  2.   {  
  3.   // ...............  
  4.   RequestData data2 = new RequestData {  
  5.   OriginalPath = context.Request.Path,  
  6.   HttpHandler = httpHandler  
  7.   };  
  8.   context.Items[_requestDataKey] = data2;  
  9.   context.RewritePath("~/UrlRouting.axd");  
  10.   } 

  再來(lái)看看OnApplicationPostMapRequestHandler方法中,最終做了以下調(diào)用:

 
 
 
 
  1.   public virtual void PostMapRequestHandler(HttpContextBase context)  
  2.   {  
  3.   RequestData data = (RequestData)context.Items[_requestDataKey];  
  4.   if( data != null ) {  
  5.   context.RewritePath(data.OriginalPath);  
  6.   context.Handler = data.HttpHandler;  
  7.   }  
  8.   } 

  看到了嗎,HttpContext.Items為Module在不同的事件中保存了臨時(shí)數(shù)據(jù),而且很方便。

  強(qiáng)大的背后也有麻煩事

  前面我們看到了HttpContext的強(qiáng)大,而且還提供HttpContext.Current這個(gè)靜態(tài)屬性。這樣一來(lái),的確是【在任何地方都能找到它】。想想我們能做什么?我們可以在任何一個(gè)類庫(kù)中都可以訪問(wèn)QueryString, Form,夠靈活吧。我們還可以在任何地方(比如BLL中)調(diào)用Response.Redirect()讓請(qǐng)求重定向,是不是很強(qiáng)大?

  不過(guò),有個(gè)很現(xiàn)實(shí)的問(wèn)題擺在面前:到處訪問(wèn)這些對(duì)象會(huì)讓代碼很難測(cè)試。原因很簡(jiǎn)單:在測(cè)試時(shí),這些對(duì)象沒(méi)法正常工作,因?yàn)镠ttpRuntime很多幕后的事情還沒(méi)做,沒(méi)有運(yùn)行它們的環(huán)境。是不是很掃興?沒(méi)辦法,現(xiàn)在的測(cè)試水平很難駕馭這些功能強(qiáng)大的對(duì)象。

  很多人都說(shuō)WebForms框架搞得代碼沒(méi)法測(cè)試,通常也是的確如此。

  我看到很多人在頁(yè)面的CodeFile中寫(xiě)了一大堆的控件操作代碼,還混有很多調(diào)用業(yè)務(wù)邏輯的代碼,甚至在類庫(kù)項(xiàng)目中還中訪問(wèn)QueryString, Cookie。再加上諸如ViewState, Session這類【有狀態(tài)】的東西大量使用,這樣的代碼是很難測(cè)試。

  換個(gè)視角,看看MVC框架為什么說(shuō)可測(cè)試性會(huì)好很多,理由很簡(jiǎn)單,你很少會(huì)需要使用HttpRequest, HttpRespons,從Controller開(kāi)始,您需要的數(shù)據(jù)已經(jīng)給您準(zhǔn)備好了,直接用就可以了。但MVC框架并不能保證寫(xiě)的代碼就一定能方便的測(cè)試,比如:您繼續(xù)使用HttpContext.Current.XXXXX而不使用那些HttpXxxxxBase對(duì)象。

  一般說(shuō)來(lái),很多人會(huì)采用三層或者多層的方式來(lái)組織他們的項(xiàng)目代碼。此時(shí),如果您希望您的核心代碼是可測(cè)試的,并且確實(shí)需要使用這些對(duì)象,那么應(yīng)該盡量集中使用這些強(qiáng)大的對(duì)象,應(yīng)該在最靠近UI層的地方去訪問(wèn)它們??梢园颜{(diào)用業(yè)務(wù)邏輯的代碼再提取到一個(gè)單獨(dú)的層中,比如就叫“服務(wù)層”吧,由服務(wù)層去調(diào)用下面的BLL(假設(shè)BLL的API的粒度較小),服務(wù)層由表示層調(diào)用,調(diào)用服務(wù)層的參數(shù)由表示層從HttpRequest中取得。需要操作Response對(duì)象時(shí),比如:重定向這類操作,則應(yīng)該在表示層中完成。

  記住:只有表示層才能訪問(wèn)前面提到的對(duì)象,而且要讓表示層盡量簡(jiǎn)單,簡(jiǎn)單到不需要測(cè)試,真正需要測(cè)試的代碼(與業(yè)務(wù)邏輯有關(guān))放在表示層以下。如此設(shè)計(jì),您的表示層將非常簡(jiǎn)單,以至于不用測(cè)試(MVC框架中的View也能包含代碼,但也沒(méi)法測(cè)試,是一樣的道理)。甚至,服務(wù)層還可以單獨(dú)部署。

  如果您的項(xiàng)目真的采用分層的設(shè)計(jì),那么,就應(yīng)該可以讓界面與業(yè)務(wù)處理分離。比如您可以這樣設(shè)計(jì):

  1. 表示層只處理輸入輸出的事情,它應(yīng)該僅負(fù)責(zé)與用戶的交互處理,建議這層代碼簡(jiǎn)單到可以忽略測(cè)試。

  2. 處理請(qǐng)求由UI層以下的邏輯層來(lái)完成,它負(fù)責(zé)請(qǐng)求的具體實(shí)現(xiàn)過(guò)程,它的方法參數(shù)來(lái)自于表示層。

  為了檢驗(yàn)?zāi)姆謱釉O(shè)計(jì)是否符合這個(gè)原則,有個(gè)很簡(jiǎn)單的方法:

  寫(xiě)個(gè)console小程序模擬UI層調(diào)用下層方法,能正常運(yùn)行,就說(shuō)明您的分層是正確的,否則,建議改進(jìn)它們。

  換一種方式使用ASP.NET框架

  前面我提到HttpRequest有個(gè)InputStream屬性, HttpResponse有一個(gè)OutputStream屬性,它們對(duì)應(yīng)的是輸入輸出流。直接使用它們,我們可以非常簡(jiǎn)單地提供一些服務(wù)功能,比如:我希望直接使用JSON格式來(lái)請(qǐng)求和應(yīng)答。如果采用這種方案來(lái)設(shè)計(jì),我們只需要定義好輸入輸出的數(shù)據(jù)結(jié)構(gòu),并使用這們來(lái)傳輸數(shù)據(jù)就好了。當(dāng)然了,也有其它的方法能實(shí)現(xiàn),但它們不是本文的主題,我也比較喜歡這種簡(jiǎn)單又直觀地方式來(lái)解決某些問(wèn)題。

  2007年我做過(guò)一個(gè)短信的接口,人家就提供幾個(gè)URL做為服務(wù)的地址,調(diào)用參數(shù)以及返回值就直接通過(guò)HTTP請(qǐng)求一起傳遞。

  2009年做過(guò)一個(gè)項(xiàng)目是調(diào)用Experian Precise ID服務(wù)(Java寫(xiě)的),那個(gè)服務(wù)也直接使用HTTP協(xié)議,數(shù)據(jù)格式采用XML,輸出輸入的數(shù)據(jù)結(jié)構(gòu)由他們定義的自定義類型。

  2010年,我做過(guò)一個(gè)數(shù)據(jù)訪問(wèn)層服務(wù),與C++的客戶端通信,采用ASP.NET加JSON數(shù)據(jù)格式的方式。

  基本上這三個(gè)項(xiàng)目都有一個(gè)共同點(diǎn):直接使用HTTP協(xié)議,數(shù)據(jù)結(jié)構(gòu)有著明確的定義格式,直接隨HTTP一起傳遞。就這么簡(jiǎn)單,卻非常有用,而且適用性很廣,基本上什么語(yǔ)言都能很好地相互調(diào)用。

  下面我以一個(gè)簡(jiǎn)單的示例演示這二個(gè)屬性的強(qiáng)大之處。

  在示例中,服務(wù)端要求數(shù)據(jù)的輸入輸出采用JSON格式,服務(wù)的功能是一個(gè)訂單查詢功能,輸入輸出的類型定義如下:

 
 
 
 
  1.   // 查詢訂單的輸入?yún)?shù)  
  2.   public sealed class QueryOrderCondition  
  3.   {  
  4.   public int? OrderId;  
  5.   public int? CustomerId;  
  6.   public DateTime StartDate;  
  7.   public DateTime EndDate;  
  8.   }  
  9.   // 查詢訂單的輸出參數(shù)類型  
  10.   public sealed class Order  
  11.   {  
  12.   public int OrderID { get;set;}  
  13.   public int CustomerID { get;set;}  
  14.   public string CustomerName { get;set;}  
  15.   public DateTime OrderDate { get;set;}  
  16.   public double SumMoney { get;set;}  
  17.  public string Comment { get;set;}  
  18.   public bool Finished { get;set;}  
  19.   public ListDetail { get;set;}  
  20.   }  
  21.   public sealed class OrderDetail  
  22.   {  
  23.   public int OrderID { get;set;}  
  24.   public int Quantity { get;set;}  
  25.   public int ProductID { get;set;}  
  26.   public string ProductName { get;set;}  
  27.   public string Unit { get;set;}  
  28.   public double UnitPrice { get;set;}  
  29.   } 

  服務(wù)端的實(shí)現(xiàn):創(chuàng)建一個(gè)QueryOrderService.ashx,具體實(shí)現(xiàn)代碼如下:

 
 
 
 
  1.   public class QueryOrderService : IHttpHandler  
  2.   {  
  3.   public void ProcessRequest(HttpContext context)  
  4.   {  
  5.   context.Response.ContentType = "application/json";  
  6.   string input = null;  
  7.   JavaScriptSerializer jss = new JavaScriptSerializer();  
  8.   using( StreamReader sr = new StreamReader(context.Request.InputStream) ) {  
  9.   input = sr.ReadToEnd();  
  10.   }  
  11.   QueryOrderCondition query = jss.Deserialize(input);  
  12.   // 模擬查詢過(guò)程,這里就直接返回一個(gè)列表。  
  13.   Listlist = new List();  
  14.   for( int i = 0;i <10;i++ )  
  15.   list.Add(DataFactory.CreateRandomOrder());  
  16.   string json = jss.Serialize(list);  
  17.   context.Response.Write(json);  
  18.   } 

  代碼很簡(jiǎn)單,經(jīng)過(guò)了以下幾個(gè)步驟:

  1. 從Request.InputStream中讀取客戶端發(fā)送過(guò)來(lái)的JSON字符串,

  2. 反序列化成需要的輸入?yún)?shù),

  3. 執(zhí)行查詢訂單的操作,生成結(jié)果數(shù)據(jù),

  4. 將結(jié)果做JSON序列化,轉(zhuǎn)成字符串,

  5. 寫(xiě)入到響應(yīng)流。

  很簡(jiǎn)單吧,我可以把它看作是一個(gè)服務(wù)吧,但它沒(méi)有其它服務(wù)框架的種種約束,而且相當(dāng)靈活,比如我可以讓服務(wù)采用GZIP的方式來(lái)壓縮傳輸數(shù)據(jù):

 
 
 
 
  1.   public void ProcessRequest(HttpContext context)  
  2.   {  
  3.   context.Response.ContentType = "application/json";  
  4.   string input = null;  
  5.   JavaScriptSerializer jss = new JavaScriptSerializer();  
  6.   using( GZipStream gzip = new GZipStream(context.Request.InputStream, CompressionMode.Decompress) ) {  
  7.   using( StreamReader sr = new StreamReader(gzip) ) {  
  8.   input = sr.ReadToEnd();  
  9.   }  
  10.   }  
  11.   QueryOrderCondition query = jss.Deserialize(input);  
  12.   // 模擬查詢過(guò)程,這里就直接返回一個(gè)列表。  
  13.   Listlist = new List();  
  14.   for( int i = 0;i <10;i++ )  
  15.   list.Add(DataFactory.CreateRandomOrder());  
  16.  string json = jss.Serialize(list);  
  17.   using( GZipStream gzip = new GZipStream(context.Response.OutputStream, CompressionMode.Compress) ) {  
  18.   using( StreamWriter sw = new StreamWriter(gzip) ) {  
  19.   context.Response.AppendHeader("Content-Encoding", "gzip");  
  20.   sw.Write(json);  
  21.   }  
  22.  }  
  23.   } 

  修改也很直觀,在輸入輸出的地方,加上Gzip的操作就可以了。

  如果您想加密傳輸內(nèi)容,也可以在讀寫(xiě)之間做相應(yīng)的處理,或者,想換個(gè)序列化方式,也簡(jiǎn)單,我想您應(yīng)該懂的。

  總之,如何讀寫(xiě)數(shù)據(jù),全由您來(lái)決定。喜歡怎樣處理就怎樣處理,這就是自由。

  不僅如此,我還可以讓服務(wù)端判斷客戶端是否要求使用GZIP方式來(lái)傳輸數(shù)據(jù),如果客戶端要求使用GZIP壓縮,服務(wù)就自動(dòng)適應(yīng),最后把結(jié)果也做GZIP壓縮處理,是不是更酷?

 
 
 
 
  1.   public void ProcessRequest(HttpContext context)  
  2.   {  
  3.   context.Response.ContentType = "application/json";  
  4.   string input = null;  
  5.   JavaScriptSerializer jss = new JavaScriptSerializer();  
  6.   bool enableGzip = (context.Request.Headers["Content-Encoding"] == "gzip");  
  7.   if( enableGzip )  
  8.   context.Request.Filter = new GZipStream(context.Request.Filter, CompressionMode.Decompress);  
  9.   using( StreamReader sr = new StreamReadercontext.Request.InputStream) ) {  
  10.   input = sr.ReadToEnd();  
  11.   }  
  12.   QueryOrderCondition query = jss.Deserialize(input);  
  13.   // 模擬查詢過(guò)程,這里就直接返回一個(gè)列表。  
  14.   Listlist = new List();  
  15.   for( int i = 0;i <10;i++ )  
  16.   list.Add(DataFactory.CreateRandomOrder());  
  17.   string json = jss.Serialize(list);  
  18.   if( enableGzip ) {  
  19.   context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);  
  20.   context.Response.AppendHeader("Content-Encoding", "gzip");  
  21.   }  
  22.   context.Response.Write(json);  
  23.   } 

  注意:這次我為了不想寫(xiě)二套代碼,使用了Request.Filter屬性。前面我就說(shuō)過(guò)這是個(gè)功能強(qiáng)大的屬性。這個(gè)屬性實(shí)現(xiàn)的效果就是裝飾器模式,因此您可以繼續(xù)對(duì)輸入輸出流進(jìn)行【裝飾】,但是要保證輸入和輸出的裝飾順序要相反。所以使用多次裝飾后,會(huì)把事情搞復(fù)雜,因此,建議需要多次裝飾時(shí),做個(gè)封裝可能會(huì)好些。不過(guò),這個(gè)屬性的更強(qiáng)大之處或許在這里體現(xiàn)的并不明顯,要談它的強(qiáng)大之處已不是本文的主題,我以后再說(shuō)。

  想想:我這幾行代碼與此服務(wù)完全沒(méi)有關(guān)系,而且照這種做法,每個(gè)服務(wù)都要寫(xiě)一遍,是不是太麻煩了?

 
 
 
 
  1.   bool enableGzip = (context.Request.Headers["Content-Encoding"] == "gzip");  
  2.   if( enableGzip )  
  3.   context.Request.Filter = new GZipStream(context.Request.Filter, CompressionMode.Decompress);  
  4.  // .............................................................  
  5.   if( enableGzip ) {  
  6.   context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);  
  7.   context.Response.AppendHeader("Content-Encoding", "gzip");  
  8.   } 

  其實(shí),豈止是這一個(gè)地方麻煩。照這種做法,每個(gè)服務(wù)都要?jiǎng)?chuàng)建一個(gè)ahsx文件,讀輸入,寫(xiě)輸出,也是重復(fù)勞動(dòng)。但是,如何改進(jìn)這些地方,就不是本文的主題了,我將在后面的博客中改進(jìn)它們。今天的主題是展示這些對(duì)象的強(qiáng)大功能。

  從以上的示例中,您有沒(méi)有發(fā)現(xiàn):只要使用這幾個(gè)對(duì)象就可以實(shí)現(xiàn)一個(gè)服務(wù)所必需的基礎(chǔ)功能!

  在后續(xù)博客中,我將引入其它一些ASP.NET的基礎(chǔ)對(duì)象,并把本次實(shí)現(xiàn)的一部分處理抽取出來(lái),實(shí)現(xiàn)一個(gè)簡(jiǎn)單的服務(wù)框架。有興趣的同學(xué),可以繼續(xù)關(guān)注。

  每個(gè)對(duì)象都是一個(gè)不朽的傳奇,每個(gè)傳奇背后都有一個(gè)精彩的故事。

  我是Fish Li, 感謝大家閱讀我的博客,請(qǐng)繼續(xù)關(guān)注我的后續(xù)博客。

原文:http://www.cnblogs.com/fish-li/archive/2011/08/21/2148640.html

【編輯推薦】

  1. 詳解ASP.NET MVC 3新的Layout布局系統(tǒng)
  2. ASP.NET MVC中很酷的jQuery驗(yàn)證插件
  3. ASP.NET MVC 3新特性全解析
  4. ASP.NET MVC 3讓你瘋狂的五大理由
  5. 詳解ASP.NET MVC 3 beta新特性

新聞名稱:淺談ASP.NET核心對(duì)象
標(biāo)題網(wǎng)址:http://uogjgqi.cn/article/cocddgj.html
掃二維碼與項(xiàng)目經(jīng)理溝通

我們?cè)谖⑿派?4小時(shí)期待你的聲音

解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流