上接[翻譯]ASP.NET AJAX以內部揭祕

在瀏覽器中緩存web服務響應能夠顯著節省帶寬
瀏覽器能夠在用戶的硬盤裏緩存圖片、JavaScript、CSS文件,若是XML HTTP調用是一個HTTP GET的話也是能夠緩存的。這個緩存是基於URL的。若是是相同URL,且保存在同一個電腦裏,那麼數據將從緩存里加載,而不會向服務器再次請求。基本上,瀏覽器能夠緩存任何HTTP GET請求而且返回基於URL的被緩存數據。若是你把一個XML HTTP調用做爲HTTP Get方式的話,那麼服務端將返回一些特殊的頭信息,用於通知瀏覽器對相應作緩存,以後再次調用相同的內容,結果就會當即從緩存中被返回,從而減小了網絡傳輸延遲和下載時間。

在Pageflakes中,咱們對用戶的狀態作了緩存,因此當用戶再次訪問的時候會從瀏覽器的緩存裏當即獲得緩存數據,而不用經過服務端。所以第二次加載時間會變得很是快。咱們也緩存了用戶的某些行爲所產生的結果。當用戶再次作相同行爲時,緩存結果就會當即從用戶的本地緩存中加載,從而減小了網絡傳輸時間。用戶會體驗到一個快速加載和高響應的站點,得到速度會有明顯增加。

這個方法就是處理Atlas web service調用時要使用HTTP GET方式,而且要返回一些明確的HTTP頭信息告知瀏覽器具體要緩存多長時間。若是在響應期間你返回了一個「Expires」頭信息,那麼瀏覽器就會緩存這個XML HTTP結果。這裏你須要返回兩個頭信息去通知瀏覽器緩存結果。
HTTP/1.1 200 OK    
Expires: Fri, 1 Jan 2030    
Cache-Control: public
該信息將通知瀏覽器要緩存結果直到2030年1月1日。在你處理具備相同參數的同一個XML HTTP調用的時候,就將從電腦的緩存中加載數據,而不會經過服務端。這裏還有更多的控制緩存的高級方法。例如,有一個頭信息通知瀏覽器緩存60秒,那麼瀏覽器要在60秒以後才能接觸到服務端並獲取新的結果。當60秒後瀏覽器本地緩存過時的時候,它也會防止從代理服務器端得到已緩存的響應。
HTTP/1.1 200 OK    
Cache-Control: private, must-revalidate, proxy-revalidate, max-age=60
 
讓咱們來嘗試着在一個ASP.NET web service方法中產生這樣的頭信息:
[WebMethod][ScriptMethod(UseHttpGet= true)]
public string CachedGet()
{
        TimeSpan cacheDuration = TimeSpan.FromMinutes(1);
        Context.Response.Cache.SetCacheability(HttpCacheability.Public);
        Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
        Context.Response.Cache.SetMaxAge(cacheDuration);
        Context.Response.Cache.AppendCacheExtension(
                     "must-revalidate, proxy-revalidate");

         return DateTime.Now.ToString();
}
結果就是下列頭信息:
「Expires」頭信息被正確的設置。可是問題發生在「Cache-Control」,它顯示了「max-age」的值被設置成零,這將阻止瀏覽器從任何緩存中讀取數據。若是你真的想禁用緩存,固然要發送這樣一條Cache-Control頭信息。結果像是發生了相反的事情。

輸出的結果依然是錯的,並無被緩存
不能改變「max-age」頭信息是ASP.NET 2.0中的bug。由於「max-age」被設置成零,而「max-age」的值等於零就意味着不須要緩存,因此ASP.NET 2.0才把「Cache-Control」設置爲「private」。因此使ASP.NET 2.0返回正確的緩存響應的頭信息是不可行的。

簡短節說。反編譯HttpCachePolicy類(Context.Response.Cache對象的類)以後,我發現了以下代碼:
不知何故,this._maxAge的值會被設置成零,看一下這段代碼「if (!this._isMaxAgeSet || (delta < this._maxAge))」,它用於防止_maxAge被設置得過大。因爲這個問題,咱們需繞過SetMaxAge函數,而後使用反射去直接的設置_maxAge的值。
[WebMethod][ScriptMethod(UseHttpGet= true)]
public string CachedGet2()
{
        TimeSpan cacheDuration = TimeSpan.FromMinutes(1);

        FieldInfo maxAge = Context.Response.Cache.GetType().GetField( "_maxAge",    
                BindingFlags.Instance|BindingFlags.NonPublic);
        maxAge.SetValue(Context.Response.Cache, cacheDuration);

        Context.Response.Cache.SetCacheability(HttpCacheability.Public);
        Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
        Context.Response.Cache.AppendCacheExtension(
                         "must-revalidate, proxy-revalidate");

         return DateTime.Now.ToString();
}
 
它將返回下列頭信息:
如今「max-age」被設置成了60,因此瀏覽器將把數據緩存60秒。若是60秒內你使用了相同的調用,那麼它將返回相同的結果。下面是一個顯示從服務端返回日期的輸出結果:
1分鐘後,緩存過時而且瀏覽器再次向服務器發送一個請求。客戶端代碼以下:
function testCache()
{
        TestService.CachedGet( function(result)
        {
                debug.trace(result);
        });
}
 
這裏還有另外一個問題須要解決。在web.config文件中,你會看到ASP.NET AJAX增長了下面這個元素:
<system.web>
                <trust level="Medium"/>
 
它會防止咱們設置響應對象的_maxAge,由於它須要反射。因此你必須刪除trust元素或者設置它的level屬性爲Full
<system.web>    
        <trust level="Full"/>
 
當「this」不是你認爲的「this」的時候
Atlas回調函數不會在它們被調用的相同上下文中執行 。例如,若是你在一個JavaScript類裏像這樣使用一個web method
function SampleClass()
{
         this.id = 1;
         this.call = function()
        {
                TestService.DoSomething( "Hi", function(result)
                {
                        debug.dump( this.id );
                } );
        }
}
 
當你調用「call」方法的時候會發生什麼?你會在debug中獲得「1」嗎?不會,你將在debug中獲得「null」,由於這個「this」再也不是類的實例。這是每個人常常會犯的錯誤。這在Atlas的文檔裏仍然沒有相關說明,我發現好多開發人員都花費時間去查找這是什麼錯誤。

緣由是這樣的。咱們知道只要JavaScript事件被觸發,那麼「this」就是指致使事件發生的那個HTML元素,因此若是你像下面這樣寫的話:
function SampleClass()
{
         this.id = 1;
         this.call = function()
        {
                TestService.DoSomething( "Hi", function(result)
                {
                        debug.dump( this.id );
                } );
        }
}

<input type= "button" id= "ButtonID" onclick= "o.onclick" />
 
若是你單擊了這個按鈕,就會發現「ButtonID」代替了「1」。緣由就是這個按鈕正在調用「call」方法。因此,這個調用在按鈕對象的上下文中完成,而「this」就被映射爲這個按鈕對象。

一樣的,當XML HTTP觸發了能夠捕獲和激發回調函數的onreadystatechanged事件的時候,代碼執行仍然在XML HTTP的上下文中。它是觸發了這個事件的XML HTTP對象。結果,「this」就指向了XML HTTP對象,而不是你本身的在回調函數被聲明處的類。

爲了使回調函數在類的實例的上下文中激發,因此要讓「this」指向類的實例,你須要作以下改變。
function SampleClass()
{
         this.id = 1;
         this.call = function()
        {
                TestService.DoSomething( "Hi",    
                        Function.createDelegate( this, function(result)
                {
                        debug.dump( this.id );
                } ) );
        }
}
 
這裏的Function.createDelegate用來建立一個調用「this」上下文下的特定函數的委託。它能夠給函數提供「this」的上下文。Function.createDelegate被定義在Atlas運行時。
Function.createDelegate = function(instance, method) {
         return function() {
                 return method.apply(instance, arguments);
        }
}
HTTP POST要比HTTP GET慢,可是ASP.NET AJAX默認用的是HTTP POST
默認狀況下,ASP.NET AJAX的全部web service調用都使用HTTP POST方式。HTTP POST方式要比HTTP GET方式付出更多的代價,它經過網絡傳輸更多的字節,所以就要佔用寶貴的網絡傳輸時間,也使得ASP.NET要在服務端作一些額外處理。因此,在可能的狀況下你應該使用HTTP GET方式。可是,HTTP GET方式不容許你將對象做爲參數傳輸,你只能傳輸數字、字符串和日期。當你處理一個HTTP GET調用的時候,Atlas會構造一個被編碼的URL並使用它。因此,你不該該傳輸不少內容而使URL超過2048個字符。據我目前所知,這是任何URL的最大長度。

爲了在一個web service方法中使用HTTP GET方式,你須要用[ScriptMethod(UseHttpGet=true)]屬性修飾這個方法:
[WebMethod] [ScriptMethod(UseHttpGet= true)]    
public string HelloWorld()
{
}
 
POST與GET的另外一個問題是,POST須要兩次網絡傳輸。當你使用POST的時候,web服務器會先發送一個「HTTP 100 Continue」,這意味着web服務器已經準備好了接收內容。以後,瀏覽器纔會發送實際數據。因此,由於POST請求的初始階段要比GET方式花費更多的時間,在AJAX程序裏網絡延遲(你的電腦和服務器之間的數據傳輸)是要給與足夠重視的,由於AJAX適合處理一些小的須要在毫秒級的時間內完成的調用。不然程序會不流暢而且讓用戶以爲厭煩。

Ethereal是一個很好的工具,它能夠偵測到POST和GET的狀況下到底發生了什麼:
從上面的圖中,你能夠看到POST方式在準備發送實際數據以前,要從web服務器請求一段「HTTP 100 Continue」的確認信息,這以後纔會傳輸數據。另外一方面,GET方式傳輸數據是不須要任何確認的。

因此,當你要從服務端下載頁的某一部分、一個表格或者是一段文本之類的時候就應該使用HTTP GET方式。而若是要像web form那樣以提交的方式發送數據到服務端的話就不該該使用HTTP GET方式。


結論
上面所說的這些高級技巧都已經在Pageflakes中實現了,這裏我並無說起它的詳細實現方法,可是原理都提到了,因此,你能夠放心地使用這些技術。這些技術將節省你解決問題的時間,也許在開發環境中你歷來沒認識到這些問題,可是當你大規模部署網站以後,來自世界各地的訪問者就將面對這些問題。一開始就正確的實現這些技巧將會大大節省你的開發和客戶支持的時間。請同時關注個人博客以得到更多的技巧。
相關文章
相關標籤/搜索