ASP.NET AJAX之內部揭祕

介紹
微軟發佈的ASP.NET AJAX的Beta 2版。雖然它是一個非常強大的框架,但是當你在web 2.0的世界中要開發一個真正的AJAX web站點的話,就會遇到很多問題,而且你幾乎找不到任何相關文檔。本文中,我將介紹一些在開發Pageflakes中所學習到的高級經驗。我們將會看到ASP.NET AJAX一些功能的優缺點,如批調用(Batch Call),調用超時,瀏覽器調用擁堵問題,ASP.NET 2.0中web service響應緩存的bug等等。

本文最後更新:2006年12月22日,針對ASP.NET AJAX RC更新(譯註:適用於ASP.NET AJAX v1.0)


爲什麼要使用ASP.NET AJAX
一些人在看到Pageflakes的時候,首先問我的問題就是“爲什麼不使用Protopage或者Dojo庫?而是Atlas?”微軟的Atlas(已重命名爲ASP.NET AJAX)是一個非常有前途的AJAX框架。微軟爲了這個框架做了很多努力,製作了大量可重用的組件,這樣可以減少你的開發時間,這樣的話,只要很少的工作量即可讓你的web應用程序有一個完美的界面。它與ASP.NET融爲一體,並且兼容ASP.NET的Membership和Profile。AJAX Control Toolkit項目包括了28個擴展(譯註:現在有34個),你可以把它們拖拽到頁面上,然後通過設置一些屬性就可以產生非常酷的效果。看看那些例子你就會知道ASP.NET AJAX給我們帶來了多麼強大的功能。

在我們最初開發Pageflakes的時候,Atlas還處於幼兒階段。我們只能使用page method和Web Service方法來調用Atlas的特性。我們不得不開發我們自己的拖拽、組件構造、彈出、伸縮/展開等特性。但是現在,Atlas提供了所有這些功能,所以可以大大節省我們的開發時間。最令人驚奇的是Atlas提供了web service代理的特性。你可以指定<script>標籤到一個.asmx文件,然後你會得到一個JavaScript類,它會根據web service的定義被正確的生成。這使得添加或移除一個web service變得非常簡單,而且你在web service中添加或移除方法都不需要客戶端作任何改變。Atlas也提供了很多基於AJAX調用的控件,並且提供了在JavaScript中捕獲豐富異常信息的特性。服務器端的異常可以被精確的拋給客戶端的JavaScript代碼,你可以捕獲它們並格式化這些錯誤信息,然後顯示給用戶。Atlas與ASP.NET 2.0結合起來工作得非常出色,完全消除了整合問題。你不需要擔心page method和web service的驗證和授權問題,所以可以大大減少你的客戶端代碼的開發量(當然,也正是因爲如此,Atlas運行時也變得非常巨大),相對於其它的AJAX框架來說,你可以把更多的精力放到自己的代碼開發上來。

Atlas最近的版本可以與ASP.NET的Membership、Profile完美的結合,爲你提供了在JavaScript中登錄和註銷的特性,而不用發postback給服務器,你也可以直接從JavaScript中讀取和寫入Profile。當你在web應用程序中使用Membership和Profile的時候,這將變得非常容易,例如我們做的Pageflakes

在Atlas的早些版本中,沒有使用HTTP GET調用的方法。所有的調用都是HTTP POST,所以調用的代價是非常大的。而現在,你可以說,哪個調用是HTTP GET的(而哪個不是)。一旦你使用了HTTP GET,你就可以利用HTTP響應緩存特性,我將很快介紹這一特性。


批調用並不一定快
ASP.NET AJAX的CTP版本(之前的版本)裏有一個特性,就是允許在一個請求裏包含多個請求。它工作時你不會注意到任何事情,而且也不需要寫任何特殊的代碼。一旦你使用了批調用特性,那麼在一次批調用期間,其中所有的web service調用都會被執行,所以它將減少回發時間和總響應時間。

實際的響應時間可能減少了,但是我們感覺到的延遲卻變長了。如果有3個web service被批量調用,那麼第一個調用不會最先完成,而是所有3個調用會在相同的時間完成。如果你的每一個web service調用完成後都更新UI的話,將不會一步一步更新,而是在所有調用一起完成後再一起更新UI。結果,你不會看到UI被快速更新,而是在UI被更新之前有一個長時間的延遲。如果說調用中的任何一個(比如第3個調用)下載了大量數據,那麼在所有的3個調用完成之前用戶不會看到任何變化。所以第一個調用的執行時間幾乎接近3個調用的總執行時間。雖然減少了實際的總計處理時間,但是會感覺有更長的延遲。當每個調用都只傳輸少量數據的時候批調用是非常好的,這樣,3個小型調用就會在一次回發中執行完。

讓我們看看3個調用是如何一個一個被完成的,這將說明這些調用實際上是如何被執行的。


第二個調用到達服務端的時間要比第一個調用長,因爲第一個調用吃光了帶寬。同樣的原因,下載也就會花更多的時間。瀏覽器同時打開了兩個連接到服務器端的連接,所以在同一時間,只能處理兩個調用。一旦第一個調用或第二個調用完成後,第三個調用才能被處理。

當3個web service在一次請求中被批調用的時候:


這裏總計下載時間將會有所減少(如果IIS的壓縮功能啓用的話),並且只需一次網絡響應。所有的3個調用一次被髮往到服務端後全部執行,組合而成的三個調用的響應是在一次調用中下載。但是對於用戶來講,他們會感覺速度變慢了,因爲UI的更新發生在所有批調用完成之後。這個批調用完成的總時間總是要長於兩個調用的。並且,如果你有大量的一個又一個的UI更新,IE將會凍結一段時間,這將給用戶帶來一個糟糕的體驗。一些時候,時間較長的UI更新會導致瀏覽器出現“白屏”,但是FireFox和Opera不會有此問題。

批調用也是有一些優點的。如果你的IIS啓用了gzip壓縮功能的話,將對全部結果進行壓縮而不是分別壓縮每個結果,那麼總下載時長就會少於單獨調用的下載時長。所以,通常批調用都是一些小型調用的話會比較好。但是如果調用會發送或者返回較大數據的話,比如20KB,那麼最好就別使用批調用了。批調用還有另一個問題,比如說前兩個調用非常小,第3個調用十分大,如果這3個調用被批調用的話,那麼前兩個小的調用就要延遲很長時間,因爲第3個很大。


糟糕的調用會使好的調用超時
如果有兩個HTTP調用不知何故執行了很長的時間,那麼這兩個糟糕的調用也將會使好的調用超時,同時這兩個調用會進入隊列。這裏就有一個例子:

Imagefunction TestTimeout()
ImageImage
Image{
Image    debug.trace(
"--Start--");
Image    TestService.set_defaultFailedCallback( 
Image            
function(result, userContext, methodName)
ImageImage    
Image{
Image        
var timedOut = result.get_timedOut();
Image        
if( timedOut )
Image            debug.trace( 
"Timedout: " + methodName );
Image        
else
Image            debug.trace( 
"Error: " + methodName );
Image    }
);
Image    TestService.set_defaultSucceededCallback( 
function(result)
ImageImage    
Image{
Image        debug.trace( result );
Image    }
);
Image    
Image    TestService.set_timeout(
5000);
Image    TestService.HelloWorld(
"Call 1");
Image    TestService.Timeout(
"Call 2");
Image    TestService.Timeout(
"Call 3");
Image    TestService.HelloWorld(
"Call 4");
Image    TestService.HelloWorld(
"Call 5");
Image    TestService.HelloWorld(
null); // 這句將導致錯誤
Image
}


服務端的web service也非常簡單:

Image[WebService(Namespace = "http://tempuri.org/")]
Image[WebServiceBinding(ConformsTo 
= WsiProfiles.BasicProfile1_1)]
Image[ScriptService]
ImageImage
public class TestService : System.Web.Services.WebService Image{
Image
ImageImage    
public TestService () Image{
Image
Image        
// 如果使用設計的組件,請取消註釋以下行  
Image        
// InitializeComponent(); 
Image
    }

Image
Image    [WebMethod][ScriptMethod(UseHttpGet
=true)]
ImageImage    
public string HelloWorld(string param) Image{
Image        Thread.Sleep(
1000);
Image        
return param;
Image    }

Image    
Image    [WebMethod][ScriptMethod(UseHttpGet
=true)]
ImageImage    
public string Timeout(string param) Image{
Image        Thread.Sleep(
10000);
Image        
return param;
Image    }

Image}


我調用了服務端的名爲“Timeout”的方法,它不會做任何事情,而只是等待一個較長的時間以使調用超時。之後,我再調用一個不會超時的方法。但是你猜猜輸出的是什麼:


只有第一個調用成功了。所以,任何時候如果瀏覽器的兩個連接都處在擁堵狀態的話,那麼你期待的其他調用也都將會超時。

在Pageflakes的運營中,我們曾經幾乎每天都從客戶端得到400個到600個超時錯誤報告,我們從未發現這是怎麼發生的。起初,我們懷疑是互聯網連接過慢造成的,但是不可能如此多的用戶都發生這種情況。後來,我們猜測是主機提供商的網絡出現了問題。我們做了大量的網絡析去發現問題是否是出現在網絡上,但是我們沒有發現任何異常。我們使用了SQL Profiler去查找是否是長時間運行的查詢導致了ASP.NET請求執行時的超時。但是不幸,我們最終發現的是,大部分超時錯誤出現的情況都是先有一些壞的調用,然後好的調用也超時了。所以我們修改了Atlas運行時,引進了自動重試的功能,問題終於完全消失了。然而,自動重試需要對ASP.NET AJAX框架的Javascript做一次“心臟外科手術”,這個方法會要求每個調用在超時後都重試一次。爲了實現它,我們需要截獲所有web method調用並且在onFailure回調函數中作個鉤子,如果失敗的原因是超時,onFailure將再次調用相同的web method

另一個需要關注的發現是當我們外出旅行感到疲憊時,想通過酒店或機場的無線網絡連接到互聯網訪問Pageflakes的時候,首次訪問總是不成功,並且所有的web service調用在第一次嘗試中總是失敗。直到我們刷新之前都不會工作。這也是我們要實現web service調用立即自動重試的另一個主要原因,它正好可以解決這個問題。

這裏我會告訴你怎麼做。Sys$Net$WebServiceProxy$invoke函數是負責處理所有web service調用的。所以,我們需要通過一個自定義onFailure回調函數來替換這個函數。只要有錯誤或者超時就會激發這個自定義回調函數。所以,當有超時發生的時候,就會再次調用這個函數,重試就會發生。

ImageSys.Net.WebServiceProxy.retryOnFailure = 
Image    
function(result, userContext, methodName, retryParams, onFailure)
ImageImage
Image{
Image    
if( result.get_timedOut() )
ImageImage    
Image{
Image        
iftypeof retryParams != "undefined" )
ImageImage        
Image{
Image            debug.trace(
"Retry: " + methodName);
Image            Sys.Net.WebServiceProxy.original_invoke.apply(
this, retryParams );
Image        }

Image        
else
ImageImage        
Image{
Image            
if( onFailure ) onFailure(result, userContext, methodName);
Image        }

Image    }

Image    
else
ImageImage    
Image{
Image        
if( onFailure ) onFailure(result, userContext, methodName);
Image    }

Image}

Image
ImageSys.Net.WebServiceProxy.original_invoke 
= Sys.Net.WebServiceProxy.invoke;
ImageSys.Net.WebServiceProxy.invoke 
= 
Image    
function Sys$Net$WebServiceProxy$invoke(servicePath, methodName, useGet, 
Image        params, onSuccess, onFailure, userContext, timeout)
ImageImage
Image{   
Image    
var retryParams = [ servicePath, methodName, useGet, params, 
Image        onSuccess, onFailure, userContext, timeout ];
Image    
Image    
// 初始調用失敗
Image
    // 處理自動重試
Image
    var newOnFailure = Function.createDelegate( this
Image        
function(result, userContext, methodName) 
ImageImage        
Image
Image            Sys.Net.WebServiceProxy.retryOnFailure(result, userContext, 
Image                methodName, retryParams, onFailure); 
Image        }
 );
Image        
Image    Sys.Net.WebServiceProxy.original_invoke(servicePath, methodName, useGet, 
Image        params, onSuccess, newOnFailure, userContext, timeout);
Image}


運行的時候,它將把每個超時調用都重試一次


這裏你可以看到第一個方法成功了,所有其他超時的調用都會被重試。而且你也會看到重試一次後的調用都成功了。發生這種情況是因爲在重試中服務端的方法不會做超時處理。所以,這證明了我們的實現方法是正確的。


瀏覽器只允許同一時間內有兩個調用,此時不會執行其他任何命令
瀏覽器在同一時間內只能對一個域名處理兩個併發的AJAX調用。如果你有5個AJAX調用,那麼瀏覽器首先將會處理兩個,然後等其中一個完成後,再處理另一個調用,直到剩下的4個調用都被完成。此外,你不要指望調用的執行順序會與你處理調用的順序相同,這是爲什麼呢?


正如你所見,調用3需要下載比較大的數據,所以它所需的時間就會比調用5要長,事實上,調用5會在調用3之前執行完。

所以,在HTTP的世界裏,這些都是不可預知的。

當隊列裏有多於兩個調用的時候瀏覽器將不會響應
嘗試這樣做,在首次訪問時打開任何一個加載了大量RSS的頁(如Pageflakes, Netvibes, Protopage),在加載期間,你可以嘗試着單擊一個鏈接到另一個站點或者試着直接訪問另一個站點,那麼你就會發現瀏覽器不會有任何響應。直到瀏覽器裏所有隊列的AJAX調用都完成之後,瀏覽器才能接受另一個活動。這是IE的一個比較糟糕的地方,但是Firefox和Opera就不會有此問題。

這個問題是,當你有大量的AJAX調用的時候,瀏覽器會將所有的調用放到一個隊列裏,在同一時間內只執行其中的兩個。所以,如果你單擊了某個鏈接或者轉向另一個站點,那麼瀏覽器必須等待在得到另一個調用之前正在執行的調用完成之後纔會去處理。解決這個問題的辦法就是防止瀏覽器在同一時間內有多於兩個的調用在隊列裏。我們需要維持一個自己的隊列,然後從我們的隊列裏將一個一個調用的發到瀏覽器的隊列中。

這個解決方案是很棒,它可以防止調用間的衝突:

ImageImagevar GlobalCallQueue = Image{
Image    _callQueue : [],    
// 保存web method的調用列表
Image
    _callInProgress : 0,    // 瀏覽器目前處理的web method的編號
Image
    _maxConcurrentCall : 2// 同一時間內執行調用的最大數
Image
    _delayBetweenCalls : 50// 調用執行之間的延遲
Image
    call : function(servicePath, methodName, useGet, 
Image        params, onSuccess, onFailure, userContext, timeout)
ImageImage    
Image{
Image        
var queuedCall = new QueuedCall(servicePath, methodName, useGet, 
Image            params, onSuccess, onFailure, userContext, timeout);
Image
Image        Array.add(GlobalCallQueue._callQueue,queuedCall);
Image        GlobalCallQueue.run();
Image    }
,
Image    run : 
function()
ImageImage    
Image{
Image        
/// 從隊列裏執行一個調用
Image
        
Image        
if0 == GlobalCallQueue._callQueue.length ) return;
Image        
if( GlobalCallQueue._callInProgress < 
Image            GlobalCallQueue._maxConcurrentCall )
ImageImage        
Image{
Image            GlobalCallQueue._callInProgress 
++;
Image            
// 得到第一個調用隊列
Image
            var queuedCall = GlobalCallQueue._callQueue[0];
Image            Array.removeAt( GlobalCallQueue._callQueue, 
0 );
Image            
Image            
// 調用web method
Image
            queuedCall.execute();
Image        }

Image        
else
ImageImage        
Image{
Image            
// 達到最大併發數,不能運行另一個調用
Image
    // 處理中的webservice method
Image
        }

Image    }
,
Image    callComplete : 
function()
ImageImage    
Image{
Image        GlobalCallQueue._callInProgress 
--;
Image        GlobalCallQueue.run();
Image    }

Image}
;
Image
ImageQueuedCall 
= function( servicePath, methodName, useGet, params, 
Image    onSuccess, onFailure, userContext, timeout )
ImageImage
Image{
Image    
this._servicePath = servicePath;
Image    
this._methodName = methodName;
Image    
this._useGet = useGet;
Image    
this._params = params;
Image    
Image    
this._onSuccess = onSuccess;
Image    
this._onFailure = onFailure;
Image    
this._userContext = userContext;
Image    
this._timeout = timeout;
Image}

Image
ImageQueuedCall.prototype 
= 
ImageImage
Image{
Image    execute : 
function()
ImageImage    
Image{
Image        Sys.Net.WebServiceProxy.original_invoke( 
Image            
this._servicePath, this._methodName, this._useGet, this._params,  
Image            Function.createDelegate(
thisthis.onSuccess), // 調用處理完成
Image
            Function.createDelegate(thisthis.onFailure), // 調用處理完成
Image
            this._userContext, this._timeout );
Image    }
,
Image    onSuccess : 
function(result, userContext, methodName)
ImageImage    
Image{
Image        
this._onSuccess(result, userContext, methodName);
Image        GlobalCallQueue.callComplete();            
Image    }
,        
Image    onFailure : 
function(result, userContext, methodName)
ImageImage    
Image{
Image        
this._onFailure(result, userContext, methodName);
Image        GlobalCallQueue.callComplete();            
Image    }
        
Image}
;


QueueCall封裝了一個web method調用,它擁有真實web服務調用的所有參數,並且重寫了onSuccess和onFailure回調函數。我們想知道當一個調用完成或者失敗了的時候,如何從我們的隊列裏調出另一個調用。GlobalCallQueue保存了web服務調用的列表。無論何時,當一個web method被調用時,我們先要對GlobalCallQueue中的調用進行排隊,並從我們自己的隊列裏一個一個的執行調用。這樣就可以保證瀏覽器在相同的時間裏不會有多於兩個的調用,所以瀏覽器就不會停止響應。

爲了確保隊列是基於調用的,我們需要像之前那樣再次重寫ASP.NET AJAX的web method

ImageSys.Net.WebServiceProxy.original_invoke = Sys.Net.WebServiceProxy.invoke;
ImageSys.Net.WebServiceProxy.invoke 
= 
Image    
function Sys$Net$WebServiceProxy$invoke(servicePath, methodName, 
Image        useGet, params, onSuccess, onFailure, userContext, timeout)
ImageImage
Image{   
Image    GlobalCallQueue.call(servicePath, methodName, useGet, params, 
Image        onSuccess, onFailure, userContext, timeout);
Image}



在瀏覽器中緩存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結果。這裏你需要返回兩個頭信息去通知瀏覽器緩存結果。

ImageHTTP/1.1 200 OK 
ImageExpires: Fri, 
1 Jan 2030 
ImageCache
-Control: public


該信息將通知瀏覽器要緩存結果直到2030年1月1日。在你處理具有相同參數的同一個XML HTTP調用的時候,就將從電腦的緩存中加載數據,而不會通過服務端。這裏還有更多的控制緩存的高級方法。例如,有一個頭信息通知瀏覽器緩存60秒,那麼瀏覽器要在60秒之後才能接觸到服務端並獲取新的結果。當60秒後瀏覽器本地緩存過期的時候,它也會防止從代理服務器端獲得已緩存的響應。

ImageHTTP/1.1 200 OK 
ImageCache
-Control: private, must-revalidate, proxy-revalidate, max-age=60


讓我們來嘗試着在一個ASP.NET web service方法中產生這樣的頭信息:

Image[WebMethod][ScriptMethod(UseHttpGet=true)]
Image
public string CachedGet()
ImageImage
Image{
Image    TimeSpan cacheDuration 
= TimeSpan.FromMinutes(1);
Image    Context.Response.Cache.SetCacheability(HttpCacheability.Public);
Image    Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
Image    Context.Response.Cache.SetMaxAge(cacheDuration);
Image    Context.Response.Cache.AppendCacheExtension(
Image           
"must-revalidate, proxy-revalidate");
Image
Image    
return DateTime.Now.ToString();
Image}


結果就是下列頭信息:


“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的值。

Image[WebMethod][ScriptMethod(UseHttpGet=true)]
Image
public string CachedGet2()
ImageImage
Image{
Image    TimeSpan cacheDuration 
= TimeSpan.FromMinutes(1);
Image
Image    FieldInfo maxAge 
= Context.Response.Cache.GetType().GetField("_maxAge"
Image        BindingFlags.Instance
|BindingFlags.NonPublic);
Image    maxAge.SetValue(Context.Response.Cache, cacheDuration);
Image
Image    Context.Response.Cache.SetCacheability(HttpCacheability.Public);
Image    Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
Image    Context.Response.Cache.AppendCacheExtension(
Image            
"must-revalidate, proxy-revalidate");
Image
Image    
return DateTime.Now.ToString();
Image}


它將返回下列頭信息:


現在“max-age”被設置成了60,所以瀏覽器將把數據緩存60秒。如果60秒內你使用了相同的調用,那麼它將返回相同的結果。下面是一個顯示從服務端返回日期的輸出結果:


1分鐘後,緩存過期並且瀏覽器再次向服務器發送一個請求。客戶端代碼如下:

Imagefunction testCache()
ImageImage
Image{
Image    TestService.CachedGet(
function(result)
ImageImage    
Image{
Image        debug.trace(result);
Image    }
);
Image}


這裏還有另一個問題需要解決。在web.config文件中,你會看到ASP.NET AJAX增加了下面這個元素:

Image<system.web>
Image        
<trust level="Medium"/>


它會防止我們設置響應對象的_maxAge,因爲它需要反射。所以你必須刪除trust元素或者設置它的level屬性爲Full

Image<system.web> 
Image    
<trust level="Full"/>



當“this”不是你認爲的“this”的時候
Atlas回調函數不會在它們被調用的相同上下文中執行 。例如,如果你在一個JavaScript類裏像這樣使用一個web method

Imagefunction SampleClass()
ImageImage
Image{
Image    
this.id = 1;
Image    
this.call = function()
ImageImage    
Image{
Image        TestService.DoSomething( 
"Hi"function(result)
ImageImage        
Image{
Image            debug.dump( 
this.id );
Image        }
 );
Image    }

Image}


當你調用“call”方法的時候會發生什麼?你會在debug中得到“1”嗎?不會,你將在debug中得到“null”,因爲這個“this”不再是類的實例。這是每一個人經常會犯的錯誤。這在Atlas的文檔裏仍然沒有相關說明,我發現好多開發人員都花費時間去查找這是什麼錯誤。

原因是這樣的。我們知道只要JavaScript事件被觸發,那麼“this”就是指導致事件發生的那個HTML元素,所以如果你像下面這樣寫的話:

Imagefunction SampleClass()
ImageImage
Image{
Image    
this.id = 1;
Image    
this.call = function()
ImageImage    
Image{
Image        TestService.DoSomething( 
"Hi"function(result)
ImageImage        
Image{
Image            debug.dump( 
this.id );
Image        }
 );
Image    }

Image}

Image
Image
<input type="button" id="ButtonID" onclick="o.onclick" />


如果你單擊了這個按鈕,就會發現“ButtonID”代替了“1”。原因就是這個按鈕正在調用“call”方法。所以,這個調用在按鈕對象的上下文中完成,而“this”就被映射爲這個按鈕對象。

同樣的,當XML HTTP觸發了可以捕獲和激發回調函數的onreadystatechanged事件的時候,代碼執行仍然在XML HTTP的上下文中。它是觸發了這個事件的XML HTTP對象。結果,“this”就指向了XML HTTP對象,而不是你自己的在回調函數被聲明處的類。

爲了使回調函數在類的實例的上下文中激發,所以要讓“this”指向類的實例,你需要做如下改變。

Imagefunction SampleClass()
ImageImage
Image{
Image    
this.id = 1;
Image    
this.call = function()
ImageImage    
Image{
Image        TestService.DoSomething( 
"Hi"
Image            Function.createDelegate( 
thisfunction(result)
ImageImage        
Image{
Image            debug.dump( 
this.id );
Image        }
 ) );
Image    }

Image}


這裏的Function.createDelegate用來創建一個調用“this”上下文下的特定函數的委託。它可以給函數提供“this”的上下文。Function.createDelegate被定義在Atlas運行時。

ImageImageFunction.createDelegate = function(instance, method) Image{
ImageImage    
return function() Image{
Image        
return method.apply(instance, arguments);
Image    }

Image}



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)]屬性修飾這個方法:

Image[WebMethod] [ScriptMethod(UseHttpGet=true)] 
Image
public string HelloWorld()
ImageImage
Image{
Image}


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中實現了,這裏我並沒有提及它的詳細實現方法,但是原理都提到了,所以,你可以放心地使用這些技術。這些技術將節省你解決問題的時間,也許在開發環境中你從來沒認識到這些問題,但是當你大規模部署網站之後,來自世界各地的訪問者就將面對這些問題。一開始就正確的實現這些技巧將會大大節省你的開發

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章