在不用UpdatePanel的情形下可与ASP.NET AJAX 使用的酷UI模板技术

时间:2022-10-07 12:16:44

【原文地址】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调用,进而输出一个消息:

在不用UpdatePanel的情形下可与ASP.NET AJAX 使用的酷UI模板技术

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:

在不用UpdatePanel的情形下可与ASP.NET 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来实现的:

在不用UpdatePanel的情形下可与ASP.NET AJAX 使用的酷UI模板技术

整个应用在客户端只有8行JavaScript编码,在服务器端总共有15行编码(包括数据访问的所有代码)。所有的HTML UI生成编码是封装在4个.ascx模板文件里的,我可以从我的 webmethod 里按需加载这些模板文件并对其绑定我的数据:

在不用UpdatePanel的情形下可与ASP.NET AJAX 使用的酷UI模板技术

点击这里下载ViewManager.RenderView的编码,如果你想看看,试用一下的话。

希望本文对你有所帮助,

Scott


 

修改后可运行的示例程序:AjaxSample