用ASP.NET 2.0實現AJAX風格的Web開發

來源:互聯網
上載者:User
提要 在過去的幾個月中,基於AJAX技術開發高度互動的Web應用程式的設計模式迅速流行開來。現在,具有高度可配置性的Web應用程式,例如Google Maps和A9,都在綜合利用這些技術來創造豐富的用戶端使用者體驗。其實,結合AJAX技術進行Web開發並非最近的研究成果,只不過這些技術一直以來不斷得到持續更新和改進。

  本文中我有三個目的。首先,我想提供一個AJAX風格應用程式的進階概述。其次,我想詳細地描述ASP.NET 2.0的非同步回調機制。最後,我想對構建AJAX風格應用程式的工具和架構的未來改進作一下展望。

  歸納來看,AJAX風格的Web應用程式展示了下列特徵:

  · 到Web伺服器的非同步請求-在使用者等待來自於Web伺服器的響應時,瀏覽器使用者介面不會被堵塞,而是可以繼續響應使用者的互動。

  · 高度依賴於用JavaScript編寫的基於瀏覽器的邏輯-W3C DOM的最新改進和標準化為實現動態用戶端UI更新提供了支援。

  · 在瀏覽器和Web伺服器之間的基於XML資料的交換-XMLHttp對象使得與Web伺服器進行通訊而不需要重載頁面成為可能。

  一個AJAX應用程式和傳統型Web應用程式之間的最大差別是,每次使用者互動不會導致每一個HTTP請求都被發送到Web伺服器;而是,用JavaScript實現的基於瀏覽器的邏輯掌握著控制權,之後再由該控制決定是局部處理請求還是向伺服器作非同步呼叫。一旦到伺服器的非同步呼叫結束,用戶端邏輯立即適當更新UI的相關部分。這種方式具有下列優點:

  · 使用者體驗更為豐富。例如,當一個Google地圖使用者沿一個方向拖動地圖時,系統就會在後台向伺服器發出一個非同步請求,結果是他能夠在超出螢幕邊界後繼續拖動。這樣以來,當使用者進一步拖動地圖時,新的映像已經可用了。這導致一種響應更快的感覺。

  · 既然跨越基於XMLHttp的到伺服器的調用狀態並沒有丟失,那麼,AJAX應用程式就可以避免每次都重建UI介面。

  · 更多的邏輯位於瀏覽器端,從而減少了到Web伺服器的來回請求的數量,進而全面改進系統的潛力。

  儘管存在這麼多的優點,然而AJAX風格的應用程式還是存在一些不足之處。例如,AJAX風格應用程式的開發是比較困難的,因為缺乏相應的架構(一組類似於Windows MFC工具包的UI類)和IDE(調試,可視化設計,等等)支援。另外,基於AJAX進行開發要求一個人必須至少掌握兩種語言(DHTML和JavaScript)。而且,AJAX風格應用程式的編碼需要更長的時間,因為它需要另外的測試以使其支援多瀏覽器版本和類型。最後,由於基於JavaScript的源碼為終端使用者可存取,所以開發過程中的安全分析也變得非常重要。

  幸好,例如Atlas,AJAX.NET和Google Maps API等工具的出現為將來構建AJAX風格的應用程式提供了更好的支援。接下來,我們將討論一下,對於構建AJAX風格應用程式的支援技術的發展曆程以及我們能夠從最新發行的工具集Atlas得到怎樣的期望。

  讓我們首先討論XMLHttp對象。這個對象最初為微軟所引入,以後在其它平台(包括Mozilla和蘋果公司的Safari瀏覽器)上也得到實現。XMLHttp支援到Web伺服器的非同步請求,這樣可以允許用戶端基於JavaScript邏輯調用Web伺服器而不需要重載整個頁面。

  換句話說,在後台與Web伺服器的互動而不引起整個頁面重載是完全有可能的。

  至於XMLHttp對象的使用則相當直接。為簡單起見,讓我們僅考慮IE特定的文法。其實,XMLHttp在其它瀏覽器上的實現文法與這裡的討論也很類似。

request = new ActiveXObject("Microsoft.XMLHTTP");
if (request){ request.onreadystatechange = CallbackHandler;
 request.open("GET", URL, true);
 request.send();
}
function CallbackHandler(){
 if ((request.readyState == 4) && (request.status == 200){
  string response = request.responseXML;
  //更新UI的相關部分
 }
}

  在上面的代碼片斷中,第一步實現執行個體化Microsoft.XMLHttp類。第二步設定我們剛剛建立的XMLHttp執行個體的屬性,其中包括當XMLHttp請求完成時將得到控制的回呼函數的地址。因為我們在向伺服器作非同步呼叫(通過把open方法的第三個參數設定為true來實現),所以我們需要回呼函數的地址。在回呼函數實現過程中,我們作額外的檢查以確保完成請求。

  你從上面的範例程式碼中可以看出,以獨立方式使用XMLHttp對象是相當簡單的。然而,把XMLHttp整合到HttpPage生命週期的其它部分中是比較困難的-例如,如何確保伺服器端的方法調用能夠存取頁面中其它控制項的狀態呢?為了正確初始化這些控制項的狀態,伺服器端的回調處理需要經曆一個與回調過程類似的HttpPage生命週期。直接使用XMLHttp對象的其它挑戰是,作為開發人員,我們需要考慮不同的瀏覽器類型。幸好,ASP.NET 2.0提供了一個可重用的模式-它能夠使得存取回調功能非常容易。注意,隨同ASP.NET 2.0一同發行了若干控制項,包括GridView,TreeView等,都綜合利用了回調機制。

  讓我們先看一下伺服器端實現原理。首先,在伺服器端要定義一個新的介面IcallBackEventHandler。任何ASPX頁面(或打算支援用戶端回調的控制項)都需要實現這個ICallBackEventHandler介面。ICallBackEventHandler介面定義了一個稱為RaiseCallbackEvent的方法。這個方法使用一個字串類型的參數並且返回一個字串。

  在用戶端,為了初始化回調功能,需要調用一個特殊的JavaScript函數。你可以通過調用ClientScriptManager.GetCallbackEventReference來獲得一個到這個特殊的JavaScript函數的引用。到GetCallbackEventReference的調用將產生一個回調引用。當調用此回呼函數時,你只需要傳遞一個字串類型的參數。這是與伺服器端的RaiseCallbackEvent簽名一致的。這就是你在用戶端建立回調機制所需做的一切。其它的把用戶端回呼函數鉤(hook up)到伺服器端的IcallBackEventHandler介面的RaiseCallbackEvent方法的實現則是由架構來完成的。前面提到的初始化回調機制的特殊JavaScript函數使用了另外兩個參數(__CALLBACKPARAM和__CALLBACKID)作為回饋資料,它們分別代表傳遞到調用者的字串參數和控制項的ID。在伺服器端,ASP.NET檢測其它兩個參數的存在並且會把請求路由到適當的控制項,這將導致調用目標控制項上的RaiseCallbackEvent方法。為瞭解決前面提到的頁面上的控制項的初始化問題,ASP.NET運行時刻在服務一次回調時提供了一個簡化版本的HttpPage生命週期。這一周期包括瀏覽頁面初始化的某個具體階段,觀察狀態載入,頁面載入和回呼函數事件處理等。一旦回呼函數事件被控制項所處理,HttpPage生命週期的其它階段就會被跳過。

  為了協助更好地理解ASP.NET 2.0的回調機制,發行包中包括了一個簡單的進度條控制項,它依靠回調來決定伺服器確定的一項任務的狀態。下面的列表1顯示了該ProgressBar控制項的代碼。為了支援用戶端回呼函數,這個控制項實現了ICallbackEventHandler介面。為了示範之目的,RaiseCallbackEvent方法實現簡單地尋找儲存在會話中的一個計數器,每次給計數器加1,並且把新值返回到用戶端。最後,列表2顯示了負責初始化該回呼函數的JavaScript代碼。它使用了this.Page.ClientScript.GetCallbackEventReference來獲得一個到需要初始化回調的函數的安全引用。

  列表1:ProgressBar.cs

public class ProgressBar : System.Web.UI.Control, System.Web.UI.ICallbackEventHandler{
private int PercentCompleted{
 get
 {
  if System.Web.HttpContext.Current.Session["PercentComplete"] == null) {
   System.Web.HttpContext.Current.Session["PercentComplete"] = 1;
 } 
 else {
  System.Web.HttpContext.Current.Session["PercentComplete"] =(int)System.Web.HttpContext.Current.Session["PercentComplete"] + 1;
 }
 return (int)System.Web.HttpContext.Current.Session["PercentComplete"];
}
set
{
 System.Web.HttpContext.Current.Session["PercentComplete"] = 1;
}
}
public string RaiseCallbackEvent(string eventArguments) {
 int percent = this.PercentCompleted;
 if (percent > 100)
 {
  this.PercentCompleted = 1;
  return "completed";
 }
 else
 {
  return percent.ToString() + "%";
 }
}
protected override void OnPreRender(EventArgs e) {
 this.Page.ClientScript.RegisterClientScriptBlock(typeof(ProgressBar),
"ProgressBar", this.GetClientSideScript(), true);
 base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer) {
 System.Text.StringBuilder sb = new StringBuilder();
 sb.Append(@"<table id=""ProgressBarContainer"" bgcolor=""LightSteelBlue""
 border=""0"" width=""400"" style=""DISPLAY:none; POSITION: absolute;
 Z-INDEX: 10"">");
 sb.Append(@"<tr><td colspan=""3"" style=""padding:3px 2px 2px 10px"">");
 sb.Append(@"<font face=""Verdana, Arial, Helvetica, sans-serif"" size=""2"">");
 sb.Append(@"<span id=""ProgressBarLabel"">Uploading...</span>");
 sb.Append(@"</font></td></tr><tr><td>");
 sb.Append(@"<font size=""1""> </font></td><td bgcolor=""#999999""
 width=""100%"">");
 sb.Append(@"<table id=""ProgressBar"" border=""0"" width=""0""
 cellspacing=""0"">");
 sb.Append(@"<tr><td style=""background-image:url(progressbar.gif)"">
 <font size=""1""> </font></td>");
 sb.Append(@"</tr></table></td>");
 sb.Append(@"<td><font size=""1""> </font></td></tr>");
 sb.Append(@"<tr height=""5px""><td colspan=""3""></td></tr>");
 sb.Append(@"</table>");
 writer.Write(sb.ToString());
 base.Render(writer);
}
private string GetClientSideScript() {
 System.Reflection.Assembly dll =
 System.Reflection.Assembly.GetExecutingAssembly();
 StreamReader reader;
 reader = new StreamReader(dll.GetManifestResourceStream("ProgressBar.txt"));
 StringBuilder js = new StringBuilder(reader.ReadToEnd());
 string fp = this.Page.ClientScript.GetCallbackEventReference(this, "", "UpdateProgressBar", "");
 js.Replace("##InitiateCallBack##", fp);
 reader.Close();
 return js.ToString();
}
}

  列表2:ProgressBar.js

<script language="javascript">
var isCompleted=false;
//這個函數初始化到伺服器端的回調
function DrawProgressBar(){
 ##InitiateCallBack##;
 if (!isCompleted) {
  window.setTimeout('DrawProgressBar()',200);
 }
 else
 {
  isCompleted=false;
  document.getElementById("ProgressBarContainer").style.display = 'none';
 }
}
//當thecallback完成時,下列函數被調用
function UpdateProgressBar(percent){
 if (percent == 'completed'){
  isCompleted=true;
 }
 else{
  document.getElementById("ProgressBar").width = percent;
 }
}

  通過使用在ASP.NET 2.0提供的用戶端回呼函數,實現進度條控制項是比較直接的,因為在控制項和用戶端之間傳遞的資料僅是一個簡單的字串。然而,一旦我們把其它資料類型也添加到其中,我們就遇到在JavaScript和.NET類型系統之間不匹配的問題。遺憾的是,ASP.NET 2.0中的回呼函數實現對此並無多大協助。任何想使用多種資料類型(簡單類型和複雜類型)的應用程式,都要實現一種自己的定製模式。

  幸好,這種限制能夠通過使用一個AJAX.NET開源庫來加以克服,AJAX.NET實現了一種基於代理的方式來調用伺服器端函數。AJAX.NET定義了一種稱為AJAXMethod的定製屬性。當一個伺服器端方法用AJAXMethod加以修飾時,一個基於JavaScript的用戶端代理將被HttpHandler(它是AJAX.NET庫的一部分)自動產生。不同於ASP.NET 2.0,它支援單個參數的字串類型以便用於回調實現。AJAX.NET支援整數,字串,雙精確度數,DateTime,DataSet等多種類型。

  Bertrand Le Roy建議使用AJAX.NET來處理JavaScript和.NET類型系統之間的差別。他建立了一種稱為EcmaScriptObject的伺服器端控制項-它基於.NET技術重新建立了JavaScript類型系統。其想法是,用.NET重建一種用戶端對象圖。當轉換髮生在伺服器端時,這種方法顯得更有意義。

  即使我們有了一種型別安全的方法來調用回呼函數,但是,我們還面臨其它的挑戰。JavaScript擔當起了把AJAX應用程式的各個部分組合到一起的"膠水"的作用。當然,相應地,對JavaScript的依賴性也進一步增加。遺憾的是,儘管JavaScript是一種強有力且通用的語言,但是它並沒有實現物件導向的原則。這意味著,要實現代碼重用可能更為困難。當然,可以使用一些技巧來使JavaScript看上去更象傳統的物件導向語言。不過即使如此,要實現託管語言中的例如事件和代理等特徵仍然相當困難。

  其它困難還包括:缺乏一個可重用架構來進一步提高JavaScript的開發效率。如果有一種基於JavaScript的能夠隱蔽不同執行環境區別的UI架構或許更好些。另外,如果能夠建立一組類,它們可以用一種安全的方式(相對於手工編碼SOAP包並使用XMLHttp來傳遞它們)來調用Web服務,也會相當不錯。

  最近來自微軟的Atlas工程確保要重點解決這類問題。這是一種極大程度地簡化AJAX風格開發的偉大嘗試。Atlas提供了一種新的JavaScript架構(注意,下面是基於微軟的一次初步宣布,以後有可能發生改變)-UI開發套件。這其中包括:支援諸如拖放和資料繫結等特徵的常用控制項;調用Web服務的SOAP棧;隱蔽瀏覽器差別的瀏覽器安全色層;包括例如本地緩衝等內容的用戶端構建模組。另外,ASP.NET團隊還計劃為ASP.NET開發其它構建模組,例如組態管理,成員管理等,以便把它們用作Web服務端點,從而實現可以直接從JavaScript中對Web服務進行存取-例如可以容易地從用戶端存取個人資訊。最後,Atlas工程還計劃擴充JavaScript文法以便包括介面、生命週期管理和multicast事件。

  據說,接下來的幾個月將是令AJAX開發人員激動的日子。因此,我非常希望本文能夠激起您對AJAX的興趣,並在你以後構建下一代Web應用程式時優先考慮使用這一技術。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.