技巧和訣竅:在不用UpdatePanel的情形下可與ASP.NET AJAX 使用的酷UI模板技術

【原文地址】Tip/Trick: Cool UI Templating Technique to use with ASP.NET AJAX for non-UpdatePanel scenarios
【原文發表日期】 Sunday, October 22, 2006 9:02 PM

這個週末我一直饒有興趣地在玩ASP.NET AJAX Beta版

通常情形下,當我把AJAX功能集成進我的編碼時,我最後總是使用 ASP.NET AJAX 提供的內置服務器端控件(譬如UpdatePanel和UpdateProgress等)以及ASP.NET AJAX控件工具包裏的那些酷控件。Scott Hanselman 兩個星期前在他最近的一次 podcast 中採訪我時開玩笑地聲稱,使用這些AJAX控件簡直是“作弊(cheating)”, 因爲在大多數常見的情形下,它們不需要你寫任何的客戶端JavaScript編碼。

這個週末,我決定把我的編程集中在 ASP.NET AJAX 框架中根本不使用UpdatePanel的一些客戶端JavaScript 庫函數上,試驗另外的方式用服務器來輕鬆地產生HTML UI,然後把這些HTML通過AJAX動態地注入頁面內。在這個過程中,我建立了一個我認爲是比較有用的庫,這個庫可以和ASP.NET AJAX 以及其他 AJAX 庫一起使用,來提供一個很好的ASP.NET模板UI機制,它不使用也不需要象 postback 和 viewstate 這樣的概念,但仍舊提供了控件封裝和簡易重用的好處。

首先,ASP.NET AJAX中JavaScript 網絡層(Networking Stack)的一些簡短的背景知識

在開始討論我上面提到的模板方法之前,先提供一些ASP.NET AJAX中客戶端JavaScript庫方面的背景知識,讓我們首先來創建一個簡單的AJAX "hello world" 應用。這個應用允許用戶輸入一個名字,點擊一個按鈕,然後在客戶端使用JavaScript向服務器做一個AJAX調用,進而輸出一個消息:

ASP.NET AJAX 包含了一個非常靈活的JavaScript 網絡庫 (network library stack),對.NET 數據類型有着豐富的序列化支持。你可以在服務器端定義可從客戶端JavaScript 裏調用的方法,要麼是你的 ASP.NET 頁面類裏的靜態方法,要麼給你的ASP.NET應用添加一個web服務,這個服務須飾以 [Microsoft.Web.Script.Services.ScriptService] 元數據屬性,而呈示的方法則須飾以標準的 [WebMethod] 屬性。

例如,下面是個SimpleService.asmx web服務,內含一個GetMessage方法,該方法接受一個字符串參數:

 

using System;
using 
System.Web.Services;

[Microsoft.Web.Script.Services.ScriptService]
public class SimpleService : WebService {

    [WebMethod]
    
public string GetMessage(string name) {
        
return "Hello <strong>" + name + "</strong>, the time here is: " + DateTime.Now.ToShortTimeString();
    
}
}

 

ASP.NET AJAX 然後可以自動創建一個JavaScript代理類,可在客戶端用來調用這個方法,以及傳遞合適的參數。添加這個JavaScript代理類最容易的方法是在頁面上添加一個 <asp:ScriptManager> 控件,然後指向web服務的端點。(這個控件同時也確保每個庫在頁面只被加載一次。)

然後我就可以調用這個方法,把文本框裏的值傳給它,用象下面這樣的客戶端 JavaScript 編碼設置好一個回調事件處理器,定在服務器響應時觸發。注:我可以把 JavaScript 編碼寫得更加花哨,去掉其中的幾行代碼,但我目前是故意要保持清晰和簡單,以避免故弄玄虛:

 

<html>
<head id="Head1" runat="server">
    
<title>Hello World Service</title>
    
<link href="StyleSheet.css" rel="stylesheet" type="text/css" />
    
    <
script language="javascript" type="text/javascript">
        
        
function callServer() {
            SimpleService.GetMessage( $
get("Name").value, displayMessageCallback );
        
}
    
        
function displayMessageCallback(result) {
            $
get("message").innerHTML result;
        
}
    
    
</script>
                
</head>
<body>
    
<form id="form1" runat="server">
        
        
<asp:ScriptManager ID="ScriptManager1" runat="server" >
            
<Services>
                
<asp:ServiceReference Path="~/SimpleService.asmx" />
            </
Services>
        
</asp:ScriptManager>
        
        
<h1>Hello World Example</h1>
        
        
<div>
            Enter Name: 
<input id="Name" type="text" />
            
            <
a href="BLOCKED SCRIPTcallServer()">Call Server</a>

            
<div id="message"></div>
        
</div>
        
    
</form>
</body>
</html>

 

這樣,當我運行這個頁面,輸入一個名字,Scott,頁面就會使用AJAX 回調,動態更新頁面上的HTML,而不需要任何postback或頁面更新。

使用模板產生HTML的一個比較乾淨的的做法

你可以從上面的例子看出,我可以很輕鬆地從服務器端返回HTML,在客戶端把它注入頁面。但是,我的這個做法的一個缺點是,我把生成HTML的邏輯直接參雜到我的服務器web method裏了。這個做法不好,因爲,1) 混雜了UI和邏輯編碼,2) 隨着 UI 愈加豐富,編碼將會變得難以維護和編寫。

我想要的是一個簡易的方法,在我的web service方法裏執行我的邏輯,獲取數據,然後把數據傳給某個模板或視圖類來生成要返回的 HTML UI 結果。譬如,考慮生成一個客戶/訂單管理的應用,在其中使用AJAX來生成類似這樣的一個客戶列表UI:

我想要在我的 WebService 裏編寫象下面這樣的服務器端編碼來按國家查詢客戶,然後返回一個合適的HTML列表UI。注意到下面的ViewManager.RenderView 方法是如何允許我傳進一個數據對象來綁定UI的。所有的UI生成編碼都移出了我的控制器webmethod,都封裝在我的View裏了:

 

    [WebMethod]
    
public string GetCustomersByCountry(string country)
    {
        CustomerCollection customers
DataContext.GetCustomersByCountry(country);

        if 
(customers.Count > 0)
            
return ViewManager.RenderView("customers.ascx", customers);
        else
            return 
ViewManager.RenderView("nocustomersfound.ascx");
    
}

結果是,這並不是很難,只需要20行左右的代碼就實現了 ViewManager 類和上面用到的 RenderView 方法。你可以在這裏下載這個簡單的實現

我的實現允許你使用標準的ASP.NET 用戶控件 (.ascx 文件)模型來定義一個顯示模板,這意味著你擁有完全的VS設計器支持, intellisense,和編譯檢查。它並不要求你一定要用一個頁面來包含這個用戶控件,實際上,我的 RenderView 實現在顯示時動態地生成一個空Page對象來包含這個用戶控件,把顯示記錄下來,以一個字符串的形式返回。

譬如,下面這個Customer.ascx模板,我可以用它來生成象上面那個截圖裏的客戶列表輸出。它生成了一串客戶的名字,每個客戶有一個連接,指向他們的訂單歷史細節:

 

<%@ Control Language="C#" CodeFile="Customers.ascx.cs" Inherits="Customers" %>

<div class="customers">

    
<asp:Repeater ID="Repeater1" runat="server">
        
<ItemTemplate>
        
            
<div>    
                
<a href="BLOCKED SCRIPTCustomerService.GetOrdersByCustomer('<%# Eval("CustomerID") %>', displayOrders)">
                    
<%# Eval("CompanyName") %>
                
</a>
            
</div>

        
</ItemTemplate>
    
</asp:Repeater>

</div>

 

相關的後端代碼是這樣的(注,如果我想的話,我可以往裏面添加特定視圖的格式化方法):

 

using System;

public 
partial class Customers : System.Web.UI.UserControl
{
    
public object Data;

    void 
Page_Load(object sender, EventArgs e)
    {
        Repeater1.DataSource 
Data;
        
Repeater1.DataBind();
    
}
}

 

爲了把數據傳進模板,(譬如,上面這個customers 集合),我一開始時要求每個UserControl 實現一個IViewTemplate 接口,通過它我可以將數據與UserControl 相關聯。但玩了一陣後,我決定使用一個更簡單的用戶模型,讓UserControl 呈示一個如上面所示的公開的Data屬性。然後,ViewManager.RenderView 方法通過反射把傳給它本身的數據對象與UserControl實例相關聯,之後,UserControl的行爲就象一個普通用戶控件一樣。

最後得到的結果是一個非常強有力而且簡易的方式,它可以生成你想要的任何類型的HTML回覆,而且非常乾淨地封裝在.ascx 模板文件裏了。

最後的加工

你可以在這裏下載我最後建立的樣例的完整編碼。爲好玩,我給上面那個客戶列表例子另添加了功能,在按國家查詢返回客戶列表後,用戶可以點擊任何一個客戶的名字,然後就跳出一個相應客戶的訂單表(還有他們下訂單的日期)。這也是用我上面描述的方法完全通過AJAX來實現的:

整個應用在客戶端只有8行JavaScript編碼,在服務器端總共有15行編碼(包括數據訪問的所有代碼)。所有的HTML UI生成編碼是封裝在4個.ascx模板文件裏的,我可以從我的 webmethod 裏按需加載這些模板文件並對其綁定我的數據:

點擊這裏下載ViewManager.RenderView的編碼,如果你想看看,試用一下的話。

希望本文對你有所幫助,

Scott

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