转自MSDN http://msdn.microsoft.com/zh-cn/magazine/cc546561.aspx
作者:Dino Esposito
对于 ASP.NET AJAX 应用程序开发而言,人们非常乐衷于使用部分呈现,其主要原因在于其固有的简单性 — 对现有页面影响较小,对开发人员的技能要求较低。但是,部分呈现仅仅是一个在执行回发请求的过程中绕过浏览器的小技巧。使用部分呈现的页面并不是真正意义上的 AJAX 页面,因为它们仍然依赖于以前的回发方法。那么究竟什么才算是真正的 AJAX 应用程序?
实际上,真正的 AJAX 应用程序使用 XMLHttpRequest 对象绕过浏览器并与 Web 服务器及任何托管 HTTP 端点建立直接通信。该应用程序能够异步检索必要的数据并单独刷新用户界面的各个块。许多 AJAX 框架都是根据此关键行为设计的,它们包含不同数量的语法修饰、或大或小的功能集以及丰富或简单的用户界面小组件系列。ASP.NET AJAX 就是其中的一种框架。
设计和构建真正的 AJAX 应用程序并非一项容易的任务,如果缺少合适的编程工具,甚至会更加困难。与其他 AJAX 框架相比,ASP.NET AJAX 的优越性体现在它为访问服务器端代码所提供的支持上。从开发人员的角度来看,使用 ASP.NET AJAX 连接到可编写脚本的 Web 或 Windows
® Communication Foundation (WCF) 服务就像孩子玩游戏一样简单。当您引用 URL 时,框架会为您生成 JavaScript 代理类。这做起来轻松而有效。代理类隐藏 XMLHttpRequest 细节、序列化和反序列化、数据格式以及 HTTP 打包的所有细节。您只需异步调用一个方法,即可获得自身具备属性集的纯数据传输对象 (DTO)。
但是,ASP.NET AJAX 并未提供这么有效率且可在浏览器中使用下载数据的机制。当然,这会很快得到改善,但是现在 ASP.NET AJAX 提供的客户端 UI 模型确实不如服务器端服务模型那么丰富。
那么目前如何才能改进 ASP.NET AJAX 纯 Web 应用程序的 UI 模型呢?上个月,我介绍了单页界面 (SPI) 的概念并提供了几个可用于构建 SPI 页的简单模式。本月,我将讨论客户端模板和数据绑定。
典型的 AJAX 功能
实时检索股市报价的应用程序是说明 AJAX 方法强大功能的典型示例。如今,提供此功能的非 AJAX 网站要么使用中继刷新标记,要么使用基于插件的方法(如 Flash、Silverlight™ 或 ActiveX
® 控件)。通常,首先将要显示的股票价值列表绑定到服务器端网格控件上,然后在下一次回发或页面请求过程中将该网格与页面其余部分一同刷新。使用部分呈现可以快速改善此类页面。让我们了解一下如何操作。
图 1 中的标记将网格控件封装在一个 UpdatePanel 控件中,Timer 控件会定期刷新该控件。
图 1 实时股市报价页面的 UI
<asp:UpdatePanel runat="server" ID="UpdatePanel1" UpdateMode="Conditional"> <ContentTemplate> <div style="margin:10px;"> <asp:Label runat="server" ID="Label2" /> <asp:GridView ID="GridView1" runat="server" SkinID="ClassicGrid" AutoGenerateColumns="false" OnRowDataBound="GridView1_RowDataBound"> <Columns> <asp:BoundField DataField="Symbol" HeaderText="SYMBOL" /> <asp:BoundField DataField="Quote" HeaderText="LAST" > <ItemStyle HorizontalAlign="Right" /> </asp:BoundField> <asp:BoundField DataField="Change" HeaderText="CHANGE" > <ItemStyle HorizontalAlign="Right" /> </asp:BoundField> </Columns> </asp:GridView> <small><asp:Label runat="server" ID="Label1" /></small> </div> </ContentTemplate> <Triggers> <asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" /> </Triggers> </asp:UpdatePanel> <asp:Timer runat="server" id="Timer1" Enabled="true" Interval="600000" OnTick="Timer1_Tick" />
网格控件被绑定到对象集合中,其中的对象至少具有三个属性:Symbol、Quote 和 Change。网格内容每 10 分钟刷新一次。ASP.NET AJAX Timer 控件会激活客户端计时器,并在间隔结束时触发回发。由于网格控件由 UpdatePanel 封装,因此包含股市报价的网格会独立于页面进行更新。在服务器上,计时器的 Tick 事件会运行下列代码:
protected void Timer1_Tick(object sender, EventArgs e) { Label2.Text = "<b>Last update: </b>" + DateTime.Now.ToLocalTime(); GridView1.DataSource = picker.Update(); GridView1.DataBind(); }
选取器对象是名为 StockPicker 的自定义帮助程序类的实例。此类包含一个负责检索报价的服务类实例。以下是 StockPicker 类的构造函数:
public StockPicker(string stocks) { string provider = ConfigurationManager.AppSettings["StockProvider"]; _service = GetFactory(serviceProvider, stocks); if (_service == null) return; _desc = _service.GetServiceDescription(); }
选取器从配置文件读取服务提供程序类的名称并将其实例化。选取器通过 IQuoteService 接口表示的约定与服务类进行通信:
interface IQuoteService { StockInfoCollection GetQuotes(); string GetServiceDescription(); }
本专栏随附的示例代码使用伪报价服务,该服务只返回随机数字,而不是股市报价。但是,可以轻松配置页面代码以使其使用真正的报价服务。您只需创建实现 IQuoteService 接口的类并连接到实际的股票数据服务即可。在示例代码中,可绑定的数据表示为 StockInfo 对象的集合:
public class StockInfo { public string Symbol { get; set; } public string Quote { get; set; } public string Change { get; set; } public string Day { get; set; } public string Time { get; set; } }
图 2 显示了运行中的基于此 API 的示例 ASP.NET AJAX 页面。
图 2
使用部分呈现的实时报价页(单击图像可查看大图)
部分呈现的限制
从功能上讲,
图 2 中的页面会按预期方式运行。如果您对其性能和总体行为感到满意,那最好不过了。但是,这样的页面存在一些弊端,虽然不是很严重,却值得注意。
首先,每次更新页面时都会移动大量的数据。数据量对于此类数据相对较少的页面(每 10 分钟刷新一次)来说可能不是问题。如果使用 Fiddler 或 Web Development Helper 进行监视,您会发现下载时的负载大小为 3KB 左右,上传时的负载大小略小一些。同样,如果您对这些数字感到满意,则可以放心使用部分呈现了。
但是,如果您查看页面的内部机制,则这些数字会让您颇感失望。页面的视图状态会在每次更新时来回切换。响应包含数据和布局信息。此外,数据已与周围的标记融合在一起,无法对其加以区分。最后,如果页面上还包含其他功能相似的可更新面板,则在同时启动其他部分呈现操作时,计时器可能会停止一段时间。或者,它可能在计时器触发时停止另一挂起操作。
最后,尽管部分呈现可以快速掌握和应用,但在需要支持同时发出的异步调用时,部分呈现就不是可采用的理想机制了。让我们了解一下如何使用纯 AJAX 方法重新编写
图 2 中的页面。
重新考虑实时报价页
纯 AJAX 方法对实时报价页的设计有什么影响呢?该页面可设置计时器并使用 XMLHttpRequest 调用远程服务。此服务是应用程序后端的一部分,将使用标准 Microsoft
® .NET Framework API 调用财务 Web 服务并获取数据。然后,数据将作为 JavaScript 对象返回到浏览器中。最后,由您决定是否向用户呈现该对象。
有何区别?首先,调用的 URL 不是页面本身。页面调用由可编写脚本的 Web 或 WCF 服务支持的 HTTP 端点。由于没有页面生命周期、回发事件和视图状态恢复,网络流量明显减少。在此示例页面中,负载比部分呈现的负载要小 10 倍。从体系结构角度来看,您可以看到两个完全分开的代码块正在运行,即客户端前端和服务器端后端。前者受 JavaScript 支持;后者基于托管代码。
ASP.NET AJAX 统一了编程接口和数据类型,这样无论在服务器上定义了哪些编程接口和约定,JavaScript 客户端开发人员都可以看到完全相同的编程接口和约定,因此,ASP.NET AJAX 可谓是此上下文中的一大亮点。JavaScript Object Notation (JSON) 层可确保到达客户端的 DTO 制作从服务器发出的数据的镜像。
让我们实验一些代码。新页面包含一个客户端按钮,用户可通过单击该按钮来下载新数据。此处,我使用按钮,而不使用计时器,仅仅是因为前者比较方便:
<input type="button" id="btnRefresh" value="Live Quotes" οnclick="_getLiveQuotes()" />
然后,JavaScript 处理程序调用包装实时报价 Web 服务上的方法,并获取 StockInfo 对象的集合:
<%@ WebService Class="Samples.WebServices.LiveQuoteService" %>
图 3 显示了该服务的完整源代码。
图 3 实时报价 Web 服务
namespace Samples.WebServices { [WebService(Namespace = "http://samples.ajax/")] [ScriptService] public class LiveQuoteService : System.Web.Services.WebService { private static string stocks = ConfigurationManager.AppSettings["StockSymbols"]; private static StockPicker picker = new StockPicker(stocks); public LiveQuoteService() { } [WebMethod] public StockInfoCollection Update() { return picker.Update(); } [WebMethod] public string GetServiceDescription() { return picker.GetServiceDescription(); } } }
在部分呈现中,用户界面元素(视图)和核心应用程序逻辑(模型)之间不存在自然分离。所有内容都集中在服务器上,供客户端随时使用。使用纯 AJAX 体系结构,表示层更加智能和丰富,并从物理上与业务层分离。不用说,此类高度分离的体系结构本身就很灵活,并且易于测试。此外,您还可以在其他位置缓存数据,如
图 4 所示。
图 4
AJAX 体系结构中可能的缓存级别(单击图像可查看大图)
可以将数据缓存到服务器上,由 (Web) 服务前端和实现转交。此外,数据也可由 HTTP 代理缓存,甚至在浏览器中执行的 JavaScript 程序代码缓存。更重要的是,您没有缓存标记;您将缓存的是可以根据运行时条件随时进行检查的可用数据(对象或 JSON 字符串)。
将呈现逻辑移动到客户端之所以非常有益,也是因为这样可以减轻繁忙服务器的负担。如果服务器只需返回较小的数据块,而无需执行包括标记生成和视图状态处理的完整页面生命周期,则它可以节省 CPU 周期和内存。
在客户端使用 ASP.NET AJAX 与远程服务进行交互并将实时数据返回到浏览器中,同样非常简单。例如,下列代码可调用远程方法:
function _getLiveQuotes() { Samples.WebServices.LiveQuoteService.Update(onDataAvailable); } function onDataAvailable(results) { // Update the UI }
ASP.NET AJAX 为您提供了与服务器端服务同名的 JavaScript 代理对象和大量静态方法。在上述代码段中,Update 方法最终会提取 JavaScript 文档类型定义 (DTD) 数组并将其传递到最终负责更新用户界面的回调函数中。代码中名为 results 的变量包含来自服务的响应 — 也就是您需要绑定到网格的数据。
遗憾的是,存在基于服务器的 ASP.NET 方法,但不存在客户端 GridView 控件。您需要某种形式的客户端数据绑定,最好包含一些模板支持。目前,ASP.NET AJAX 在这方面并没有多大的用处。有关本主题的详细说明(由 Microsoft ASP.NET 团队直接提供),请参阅侧栏文章“深入分析:ASP.NET AJAX”。
最近,某些主要 ASP.NET 控件供应商开始为服务器控件提供等效的客户端对象模型。如果使用这些库中的任何库,最终都会获得一个 JavaScript 对象,该对象可模拟服务器端控件,并且包含客户端数据源属性和 DataBind 方法。ASP.NET AJAX 为您提供了从客户端调用 Web 服务所需的基本框架,但是它未提供针对 AJAX 设计的完整 UI 框架,并且也不包括客户端数据绑定和模板。有关从 UI 角度对各种 AJAX 方法进行介绍的有趣资源,可以查看 Miljan Braticevic 发布的文章,网址为
go.microsoft.com/fwlink/?LinkID=116054。
ASP.NET AJAX 中的客户端模板
您是否记得“Atlas”?在早期版本(后来成为 ASP.NET AJAX Extensions)中,曾经尝试过提供客户端数据绑定和模板。事实上,这确实是一个很好的尝试。但是,由于某种原因,这一尝试并未在 ASP.NET 的最终版本中得以实现,然后被推送到 ASP.NET Futures 库中了。
基本上,旧的 Atlas 绑定模型基于 HTML 模板以及用于表示标记元素和数据字段之间的绑定的声明性语法。然后,模板和绑定在客户端控件(列表视图控件)中连接在一起。通过 XML 进行配置后,Atlas 列表视图控件将会对它检索到的所有信息进行大量的分析工作,并将行为、控件和绑定附加到标记的各个部分。
让我们从这最后一点分析构建您自己的较小但有效的数据绑定和模板框架的方式。请注意,最终输出内容必须是 HTML 标记的字符串。最初输入内容是具有一些公共属性的对象集合。您的任务是构建满足用户期望的 HTML 代码段。通常,会首先循环处理绑定集合并将模板的文本与对象的属性值连接在一起。
此方法有什么问题?实际上,此方法并没有任何问题。事实上,我想不到有任何不同的方法不是这样做的。这意味着您完全可以创建抽象化该基本例程的框架。在 AJAX 模式目录中(请访问
www.ajaxpatterns.org),定义客户端模板模型的主要步骤以浏览器端模板模式 (BST) 名义进行。
浏览器端模板模式
BST 模式的目标是将用于生成数据视图的代码与数据本身分离。长期以来,如何分离视图和数据一直是软件系统中的一大难题,该难题在模型视图控制器 (MVC) 模式的任何变体中都有其标准的解决方案。
当 MVC 和 BST 等显示模式完全不互相排斥时,您就可以认为 BST 本身与 MVC 相似,其中不涉及控制器的概念,而是通过实际分离浏览器和服务器将视图和模型分开。
图 5 显示了一个关系图,用于说明 BST 中涉及的步骤。用户触发远程调用,以将某些数据下载到客户端。数据由实例化新型组件(标记生成器)的 JavaScript 回调托管。
图 5
运行中的浏览器端模板(单击图像可查看大图)
标记生成器基于对页面文档对象模型 (DOM) 中一个或多个 HTML 模板的引用以及下载的数据返回 HTML 字符串。最终,回调在页面的 DOM 中插入该字符串。
现在让我们了解一些代码。在此实现中,BST 的核心是 JavaScript MarkupBuilder 类,该类最多可接受三种 HTML 模板,即页眉模板、页脚模板和项目模板。您可以直接从 DOM 引用这些模板,也可以将它们指定为纯字符串文字:
function pageLoad() { if (builder === null) { builder = new Samples.MarkupBuilder(); builder.loadHeader($get("header")); builder.loadFooter($get("footer")); builder.loadItemTemplate($get("item")); } }
使用不可见的 DIV,可以直接将 HTML 模板嵌入到页面中。但是,仅在块中的标记格式正确时,才可以这样做。更好的办法是将模板作为 XML 数据岛嵌入,如下显示:
<xml id="item"> <tr style="background-color:#F0FAFF;"> <td align="left">#Symbol</td> <td align="right">#Quote</td> <td align="right">#Change</td> </tr> </xml>
您可以看到,模板是一个使用自定义表示法引用绑定字段的 HTML 块。在这种情况下,我使用 #PropertyName 表达式指示绑定值的占位符。当数据可用时,您只需调用标记生成器上的 bind 方法即可:
function _getLiveQuotes() { Samples.WebServices.LiveQuoteService.Update(onDataAvailable); } function onDataAvailable(results) { var temp = builder.bind(results); $get("grid").innerHTML = temp; }
显然,在本示例中,名为 grid 的元素是最终输出内容的占位符。MarkupBuilder 类基于 Microsoft AJAX 客户端库。
图 6 显示了其完整的源代码。
图 6 MarkupBuilder 类
Type.registerNamespace('Samples'); // Class ctor Samples.MarkupBuilder = function Samples$MarkupBuilder() { Samples.MarkupBuilder.initializeBase(this); // Initializes private members this._header = ""; this._footer = ""; this._itemTemplate = ""; } Samples.MarkupBuilder = function Samples$MarkupBuilder(header, footer) { Samples.MarkupBuilder.initializeBase(this); // Initializes the private members this._header = header; this._footer = footer; this._itemTemplate = ""; } // PROPERTY:: header (String) function Samples$MarkupBuilder$get_header() { if (arguments.length !== 0) throw Error.parameterCount(); return this._header; } function Samples$MarkupBuilder$set_header(value) { var e = Function._validateParams(arguments, [{name: 'value', type: String}]); if (e) throw e; this._header = value; } // PROPERTY:: footer (String) function Samples$MarkupBuilder$get_footer() { if (arguments.length !== 0) throw Error.parameterCount(); return this._footer; } function Samples$MarkupBuilder$set_footer(value) { var e = Function._validateParams(arguments, [{name: 'value', type: String}]); if (e) throw e; this._footer = value; } // PROPERTY:: itemTemplate (String) function Samples$MarkupBuilder$get_itemTemplate() { if (arguments.length !== 0) throw Error.parameterCount(); return this._itemTemplate; } function Samples$MarkupBuilder$set_itemTemplate(value) { var e = Function._validateParams(arguments, [{name: 'value', type: String}]); if (e) throw e; this._itemTemplate = value; } // METHOD:: bind() function Samples$MarkupBuilder$bind(data) { var temp = this._generate(data); return temp; } // METHOD:: loadHeader() function Samples$MarkupBuilder$loadHeader(domElement) { var temp = domElement.innerHTML; this._header = temp; } // METHOD:: loadFooter() function Samples$MarkupBuilder$loadFooter(domElement) { var temp = domElement.innerHTML; this._footer = temp; } // METHOD:: loadItemTemplate() function Samples$MarkupBuilder$loadItemTemplate(domElement) { var temp = domElement.innerHTML; this._itemTemplate = temp; } /// /// /// PRIVATE members /// /// /// function Samples$MarkupBuilder转自MSDN http://msdn.microsoft.com/zh-cn/magazine/cc546561.aspx
作者:Dino Esposito
对于 ASP.NET AJAX 应用程序开发而言,人们非常乐衷于使用部分呈现,其主要原因在于其固有的简单性 — 对现有页面影响较小,对开发人员的技能要求较低。但是,部分呈现仅仅是一个在执行回发请求的过程中绕过浏览器的小技巧。使用部分呈现的页面并不是真正意义上的 AJAX 页面,因为它们仍然依赖于以前的回发方法。那么究竟什么才算是真正的 AJAX 应用程序?实际上,真正的 AJAX 应用程序使用 XMLHttpRequest 对象绕过浏览器并与 Web 服务器及任何托管 HTTP 端点建立直接通信。该应用程序能够异步检索必要的数据并单独刷新用户界面的各个块。许多 AJAX 框架都是根据此关键行为设计的,它们包含不同数量的语法修饰、或大或小的功能集以及丰富或简单的用户界面小组件系列。ASP.NET AJAX 就是其中的一种框架。设计和构建真正的 AJAX 应用程序并非一项容易的任务,如果缺少合适的编程工具,甚至会更加困难。与其他 AJAX 框架相比,ASP.NET AJAX 的优越性体现在它为访问服务器端代码所提供的支持上。从开发人员的角度来看,使用 ASP.NET AJAX 连接到可编写脚本的 Web 或 Windows ® Communication Foundation (WCF) 服务就像孩子玩游戏一样简单。当您引用 URL 时,框架会为您生成 JavaScript 代理类。这做起来轻松而有效。代理类隐藏 XMLHttpRequest 细节、序列化和反序列化、数据格式以及 HTTP 打包的所有细节。您只需异步调用一个方法,即可获得自身具备属性集的纯数据传输对象 (DTO)。但是,ASP.NET AJAX 并未提供这么有效率且可在浏览器中使用下载数据的机制。当然,这会很快得到改善,但是现在 ASP.NET AJAX 提供的客户端 UI 模型确实不如服务器端服务模型那么丰富。那么目前如何才能改进 ASP.NET AJAX 纯 Web 应用程序的 UI 模型呢?上个月,我介绍了单页界面 (SPI) 的概念并提供了几个可用于构建 SPI 页的简单模式。本月,我将讨论客户端模板和数据绑定。
典型的 AJAX 功能实时检索股市报价的应用程序是说明 AJAX 方法强大功能的典型示例。如今,提供此功能的非 AJAX 网站要么使用中继刷新标记,要么使用基于插件的方法(如 Flash、Silverlight™ 或 ActiveX ® 控件)。通常,首先将要显示的股票价值列表绑定到服务器端网格控件上,然后在下一次回发或页面请求过程中将该网格与页面其余部分一同刷新。使用部分呈现可以快速改善此类页面。让我们了解一下如何操作。 图 1 中的标记将网格控件封装在一个 UpdatePanel 控件中,Timer 控件会定期刷新该控件。图 1 实时股市报价页面的 UI<asp:UpdatePanel runat="server" ID="UpdatePanel1" UpdateMode="Conditional"> <ContentTemplate> <div style="margin:10px;"> <asp:Label runat="server" ID="Label2" /> <asp:GridView ID="GridView1" runat="server" SkinID="ClassicGrid" AutoGenerateColumns="false" OnRowDataBound="GridView1_RowDataBound"> <Columns> <asp:BoundField DataField="Symbol" HeaderText="SYMBOL" /> <asp:BoundField DataField="Quote" HeaderText="LAST" > <ItemStyle HorizontalAlign="Right" /> </asp:BoundField> <asp:BoundField DataField="Change" HeaderText="CHANGE" > <ItemStyle HorizontalAlign="Right" /> </asp:BoundField> </Columns> </asp:GridView> <small><asp:Label runat="server" ID="Label1" /></small> </div> </ContentTemplate> <Triggers> <asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" /> </Triggers> </asp:UpdatePanel> <asp:Timer runat="server" id="Timer1" Enabled="true" Interval="600000" OnTick="Timer1_Tick" />网格控件被绑定到对象集合中,其中的对象至少具有三个属性:Symbol、Quote 和 Change。网格内容每 10 分钟刷新一次。ASP.NET AJAX Timer 控件会激活客户端计时器,并在间隔结束时触发回发。由于网格控件由 UpdatePanel 封装,因此包含股市报价的网格会独立于页面进行更新。在服务器上,计时器的 Tick 事件会运行下列代码:protected void Timer1_Tick(object sender, EventArgs e) { Label2.Text = "<b>Last update: </b>" + DateTime.Now.ToLocalTime(); GridView1.DataSource = picker.Update(); GridView1.DataBind(); }选取器对象是名为 StockPicker 的自定义帮助程序类的实例。此类包含一个负责检索报价的服务类实例。以下是 StockPicker 类的构造函数:public StockPicker(string stocks) { string provider = ConfigurationManager.AppSettings["StockProvider"]; _service = GetFactory(serviceProvider, stocks); if (_service == null) return; _desc = _service.GetServiceDescription(); }选取器从配置文件读取服务提供程序类的名称并将其实例化。选取器通过 IQuoteService 接口表示的约定与服务类进行通信:interface IQuoteService { StockInfoCollection GetQuotes(); string GetServiceDescription(); }本专栏随附的示例代码使用伪报价服务,该服务只返回随机数字,而不是股市报价。但是,可以轻松配置页面代码以使其使用真正的报价服务。您只需创建实现 IQuoteService 接口的类并连接到实际的股票数据服务即可。在示例代码中,可绑定的数据表示为 StockInfo 对象的集合:public class StockInfo { public string Symbol { get; set; } public string Quote { get; set; } public string Change { get; set; } public string Day { get; set; } public string Time { get; set; } }图 2 显示了运行中的基于此 API 的示例 ASP.NET AJAX 页面。图 2 使用部分呈现的实时报价页(单击图像可查看大图)
部分呈现的限制从功能上讲, 图 2 中的页面会按预期方式运行。如果您对其性能和总体行为感到满意,那最好不过了。但是,这样的页面存在一些弊端,虽然不是很严重,却值得注意。首先,每次更新页面时都会移动大量的数据。数据量对于此类数据相对较少的页面(每 10 分钟刷新一次)来说可能不是问题。如果使用 Fiddler 或 Web Development Helper 进行监视,您会发现下载时的负载大小为 3KB 左右,上传时的负载大小略小一些。同样,如果您对这些数字感到满意,则可以放心使用部分呈现了。但是,如果您查看页面的内部机制,则这些数字会让您颇感失望。页面的视图状态会在每次更新时来回切换。响应包含数据和布局信息。此外,数据已与周围的标记融合在一起,无法对其加以区分。最后,如果页面上还包含其他功能相似的可更新面板,则在同时启动其他部分呈现操作时,计时器可能会停止一段时间。或者,它可能在计时器触发时停止另一挂起操作。最后,尽管部分呈现可以快速掌握和应用,但在需要支持同时发出的异步调用时,部分呈现就不是可采用的理想机制了。让我们了解一下如何使用纯 AJAX 方法重新编写 图 2 中的页面。
重新考虑实时报价页纯 AJAX 方法对实时报价页的设计有什么影响呢?该页面可设置计时器并使用 XMLHttpRequest 调用远程服务。此服务是应用程序后端的一部分,将使用标准 Microsoft ® .NET Framework API 调用财务 Web 服务并获取数据。然后,数据将作为 JavaScript 对象返回到浏览器中。最后,由您决定是否向用户呈现该对象。有何区别?首先,调用的 URL 不是页面本身。页面调用由可编写脚本的 Web 或 WCF 服务支持的 HTTP 端点。由于没有页面生命周期、回发事件和视图状态恢复,网络流量明显减少。在此示例页面中,负载比部分呈现的负载要小 10 倍。从体系结构角度来看,您可以看到两个完全分开的代码块正在运行,即客户端前端和服务器端后端。前者受 JavaScript 支持;后者基于托管代码。ASP.NET AJAX 统一了编程接口和数据类型,这样无论在服务器上定义了哪些编程接口和约定,JavaScript 客户端开发人员都可以看到完全相同的编程接口和约定,因此,ASP.NET AJAX 可谓是此上下文中的一大亮点。JavaScript Object Notation (JSON) 层可确保到达客户端的 DTO 制作从服务器发出的数据的镜像。让我们实验一些代码。新页面包含一个客户端按钮,用户可通过单击该按钮来下载新数据。此处,我使用按钮,而不使用计时器,仅仅是因为前者比较方便:<input type="button" id="btnRefresh" value="Live Quotes" οnclick="_getLiveQuotes()" />然后,JavaScript 处理程序调用包装实时报价 Web 服务上的方法,并获取 StockInfo 对象的集合:<%@ WebService Class="Samples.WebServices.LiveQuoteService" %>图 3 显示了该服务的完整源代码。图 3 实时报价 Web 服务namespace Samples.WebServices { [WebService(Namespace = "http://samples.ajax/")] [ScriptService] public class LiveQuoteService : System.Web.Services.WebService { private static string stocks = ConfigurationManager.AppSettings["StockSymbols"]; private static StockPicker picker = new StockPicker(stocks); public LiveQuoteService() { } [WebMethod] public StockInfoCollection Update() { return picker.Update(); } [WebMethod] public string GetServiceDescription() { return picker.GetServiceDescription(); } } }在部分呈现中,用户界面元素(视图)和核心应用程序逻辑(模型)之间不存在自然分离。所有内容都集中在服务器上,供客户端随时使用。使用纯 AJAX 体系结构,表示层更加智能和丰富,并从物理上与业务层分离。不用说,此类高度分离的体系结构本身就很灵活,并且易于测试。此外,您还可以在其他位置缓存数据,如 图 4 所示。图 4 AJAX 体系结构中可能的缓存级别(单击图像可查看大图)可以将数据缓存到服务器上,由 (Web) 服务前端和实现转交。此外,数据也可由 HTTP 代理缓存,甚至在浏览器中执行的 JavaScript 程序代码缓存。更重要的是,您没有缓存标记;您将缓存的是可以根据运行时条件随时进行检查的可用数据(对象或 JSON 字符串)。将呈现逻辑移动到客户端之所以非常有益,也是因为这样可以减轻繁忙服务器的负担。如果服务器只需返回较小的数据块,而无需执行包括标记生成和视图状态处理的完整页面生命周期,则它可以节省 CPU 周期和内存。在客户端使用 ASP.NET AJAX 与远程服务进行交互并将实时数据返回到浏览器中,同样非常简单。例如,下列代码可调用远程方法:function _getLiveQuotes() { Samples.WebServices.LiveQuoteService.Update(onDataAvailable); } function onDataAvailable(results) { // Update the UI }ASP.NET AJAX 为您提供了与服务器端服务同名的 JavaScript 代理对象和大量静态方法。在上述代码段中,Update 方法最终会提取 JavaScript 文档类型定义 (DTD) 数组并将其传递到最终负责更新用户界面的回调函数中。代码中名为 results 的变量包含来自服务的响应 — 也就是您需要绑定到网格的数据。遗憾的是,存在基于服务器的 ASP.NET 方法,但不存在客户端 GridView 控件。您需要某种形式的客户端数据绑定,最好包含一些模板支持。目前,ASP.NET AJAX 在这方面并没有多大的用处。有关本主题的详细说明(由 Microsoft ASP.NET 团队直接提供),请参阅侧栏文章“深入分析:ASP.NET AJAX”。最近,某些主要 ASP.NET 控件供应商开始为服务器控件提供等效的客户端对象模型。如果使用这些库中的任何库,最终都会获得一个 JavaScript 对象,该对象可模拟服务器端控件,并且包含客户端数据源属性和 DataBind 方法。ASP.NET AJAX 为您提供了从客户端调用 Web 服务所需的基本框架,但是它未提供针对 AJAX 设计的完整 UI 框架,并且也不包括客户端数据绑定和模板。有关从 UI 角度对各种 AJAX 方法进行介绍的有趣资源,可以查看 Miljan Braticevic 发布的文章,网址为 go.microsoft.com/fwlink/?LinkID=116054。
ASP.NET AJAX 中的客户端模板您是否记得“Atlas”?在早期版本(后来成为 ASP.NET AJAX Extensions)中,曾经尝试过提供客户端数据绑定和模板。事实上,这确实是一个很好的尝试。但是,由于某种原因,这一尝试并未在 ASP.NET 的最终版本中得以实现,然后被推送到 ASP.NET Futures 库中了。基本上,旧的 Atlas 绑定模型基于 HTML 模板以及用于表示标记元素和数据字段之间的绑定的声明性语法。然后,模板和绑定在客户端控件(列表视图控件)中连接在一起。通过 XML 进行配置后,Atlas 列表视图控件将会对它检索到的所有信息进行大量的分析工作,并将行为、控件和绑定附加到标记的各个部分。让我们从这最后一点分析构建您自己的较小但有效的数据绑定和模板框架的方式。请注意,最终输出内容必须是 HTML 标记的字符串。最初输入内容是具有一些公共属性的对象集合。您的任务是构建满足用户期望的 HTML 代码段。通常,会首先循环处理绑定集合并将模板的文本与对象的属性值连接在一起。此方法有什么问题?实际上,此方法并没有任何问题。事实上,我想不到有任何不同的方法不是这样做的。这意味着您完全可以创建抽象化该基本例程的框架。在 AJAX 模式目录中(请访问 www.ajaxpatterns.org),定义客户端模板模型的主要步骤以浏览器端模板模式 (BST) 名义进行。
浏览器端模板模式BST 模式的目标是将用于生成数据视图的代码与数据本身分离。长期以来,如何分离视图和数据一直是软件系统中的一大难题,该难题在模型视图控制器 (MVC) 模式的任何变体中都有其标准的解决方案。当 MVC 和 BST 等显示模式完全不互相排斥时,您就可以认为 BST 本身与 MVC 相似,其中不涉及控制器的概念,而是通过实际分离浏览器和服务器将视图和模型分开。 图 5 显示了一个关系图,用于说明 BST 中涉及的步骤。用户触发远程调用,以将某些数据下载到客户端。数据由实例化新型组件(标记生成器)的 JavaScript 回调托管。图 5 运行中的浏览器端模板(单击图像可查看大图)标记生成器基于对页面文档对象模型 (DOM) 中一个或多个 HTML 模板的引用以及下载的数据返回 HTML 字符串。最终,回调在页面的 DOM 中插入该字符串。现在让我们了解一些代码。在此实现中,BST 的核心是 JavaScript MarkupBuilder 类,该类最多可接受三种 HTML 模板,即页眉模板、页脚模板和项目模板。您可以直接从 DOM 引用这些模板,也可以将它们指定为纯字符串文字:function pageLoad() { if (builder === null) { builder = new Samples.MarkupBuilder(); builder.loadHeader($get("header")); builder.loadFooter($get("footer")); builder.loadItemTemplate($get("item")); } }使用不可见的 DIV,可以直接将 HTML 模板嵌入到页面中。但是,仅在块中的标记格式正确时,才可以这样做。更好的办法是将模板作为 XML 数据岛嵌入,如下显示:<xml id="item"> <tr style="background-color:#F0FAFF;"> <td align="left">#Symbol</td> <td align="right">#Quote</td> <td align="right">#Change</td> </tr> </xml>您可以看到,模板是一个使用自定义表示法引用绑定字段的 HTML 块。在这种情况下,我使用 #PropertyName 表达式指示绑定值的占位符。当数据可用时,您只需调用标记生成器上的 bind 方法即可:function _getLiveQuotes() { Samples.WebServices.LiveQuoteService.Update(onDataAvailable); } function onDataAvailable(results) { var temp = builder.bind(results); $get("grid").innerHTML = temp; }显然,在本示例中,名为 grid 的元素是最终输出内容的占位符。MarkupBuilder 类基于 Microsoft AJAX 客户端库。 图 6 显示了其完整的源代码。图 6 MarkupBuilder 类___FCKpd___13在内部,标记生成器使用 Sys.StringBuilder 对象组成 HTML 字符串。它首先添加页眉模板(如果有),然后继续循环处理绑定数据。最后,添加页脚模板。在此实现中,页眉模板和页脚模板不是数据绑定的模板。(图 7 显示了使用标记生成器的页面。)图 7 使用标记生成器的新页面(单击图像可查看大图)
其他注意事项将模板指定为纯字符串与将其指定为嵌入式 DOM 子树或 XML 数据岛是否存在区别?从标记生成器的角度来看,这并不重要,它所需要的仅仅是一个要追加到字符串生成器缓冲区的字符串。从开发人员的角度来看,使用 DOM 模板可能是更好的选择,因为您可以更轻松地验证模板,还可以从 URL 下载该模板。此处提供的解决方案仅适用于数据集合,它遵循用于客户端模板的其他自由框架使用的准则。我希望明确提出的一个框架是 PrototypeJS。要获取详细信息、示例和源代码,请访问 prototypejs.org/api/template。是否存在可以改进的方面?当然!请注意,本专栏中提供的代码仅实现用户希望通过浏览器端模板和绑定引擎获得的最小功能集。从 图 7 中可以看出,当前实现不包括用来区分上涨和下跌股票价格的彩色文字,但 图 2 中包括彩色文字。在 图 2 的代码中,我处理了 GridView 控件的 RowDataBound 事件并直接修改了相关单元格的样式:void GridView1_RowDataBound(object sender, GridViewRowEventArgs e) { if (e.Row.Cells[2].Text.StartsWith("+")) e.Row.Cells[2].ForeColor = Color.Green; if (e.Row.Cells[2].Text.StartsWith("-")) e.Row.Cells[2].ForeColor = Color.Red; }在部分呈现解决方案中,此样式在服务器上进行设置,因此可以利用服务器数据绑定控件的丰富编程模型。在客户端解决方案中,您需要可接受函数代理的更复杂的标记生成器,该函数代理在绑定数据追加到缓冲区之前设置所有绑定数据的样式。生成图 2 和图 7 的代码之间还存在一个更重要的区别,但在打印的图片中看不到。要找出此区别,请尝试运行这两个页面并单击看到的其他按钮(“Weather Forecasts”和“Start Some Task”)。 图 7 中描述的页面在检索股市报价时不会停止其他任务,并且用户通过单击来执行其他任务时不会中止服务调用。重点是什么?请记住,对于部分呈现,在每个会话中一次只能有一个未完成的请求,这与正常的非 AJAX 页面一样。当然,如果您直接通过 XMLHttpRequest 对同时产生的调用进行控制,则可以同时存在任意数量的调用。此外,如果您直接进行服务调用,则不会看到视图状态,也不必担心使其在多个部分呈现调用中保持一致的问题(这正是部分呈现实现阻止您执行并发操作的真正原因)。
HTML 消息模式在本专栏的最后,我将简要介绍一下另外一种模式 — HTML 消息模式,这种模式确实值得在将来的专栏中给予更多重视。HTML 消息模式的目标是使服务器生成要在浏览器中显示的 HTML 标记块。您可以看到,此模式介于部分呈现和其他用于构建真正 AJAX 应用程序的模型之间。如何使用此模式?一种可行的实现方法是,调用远程 URL(服务或临时 HTTP 处理程序)并接收准备显示的 HTML 代码段。请注意,它生成的流量多于纯服务调用生成的流量,但仍少于部分呈现生成的流量。在服务器上,您可以使用服务器控件的新实例(没有视图状态)组合所需的任何输出,并根据需要设置样式。我还会在将来的专栏中介绍此模式。同时,请记住,使用 ASP.NET AJAX 时,您需要编程工具来定义 AJAX 服务层并从客户端浏览器中调用它。此外,您需要功能强大的工具来有效处理客户端上的数据,如基于 JavaScript 的数据绑定和模板。您在查看我在此专栏中提供的 BST AJAX 模式的示例实现,并将其与基于部分呈现的解决方案进行比较时,必然会与我得出相同的结论。
请将您想向 Dino 咨询的问题和提出的意见发送至 cutting@microsoft.com。
Dino Esposito 是《Programming ASP.NET 3.5 Core Reference》的作者 。Dino 定居于意大利,经常在世界各地的业内活动中发表演讲。您可加入他的博客,网址为 weblogs.asp.net/despos。generate(data) { var _builder = new Sys.StringBuilder(this._header); for(i=0; i<data.length; i++) { var dataItem = data[i]; var template = this._itemTemplate; var pattern = /#/w+/g; // Finds all #word occurrences var matches = template.match(pattern); for (j=0; j<matches.length; j++) { var name = matches[j]; name = name.slice(1); template = template.replace(matches[j], dataItem[name]); } _builder.append(template); } _builder.append(this._footer); return _builder.toString(); } // PROTOTYPE Samples.MarkupBuilder.prototype = { get_header: Samples$MarkupBuilder$get_header, set_header: Samples$MarkupBuilder$set_header, get_footer: Samples$MarkupBuilder$get_footer, set_footer: Samples$MarkupBuilder$set_footer, get_itemTemplate: Samples$MarkupBuilder$get_itemTemplate, set_itemTemplate: Samples$MarkupBuilder$set_itemTemplate, bind: Samples$MarkupBuilder$bind, _generate: Samples$MarkupBuilder
转自MSDN http://msdn.microsoft.com/zh-cn/magazine/cc546561.aspx
作者:Dino Esposito
对于 ASP.NET AJAX 应用程序开发而言,人们非常乐衷于使用部分呈现,其主要原因在于其固有的简单性 — 对现有页面影响较小,对开发人员的技能要求较低。但是,部分呈现仅仅是一个在执行回发请求的过程中绕过浏览器的小技巧。使用部分呈现的页面并不是真正意义上的 AJAX 页面,因为它们仍然依赖于以前的回发方法。那么究竟什么才算是真正的 AJAX 应用程序?实际上,真正的 AJAX 应用程序使用 XMLHttpRequest 对象绕过浏览器并与 Web 服务器及任何托管 HTTP 端点建立直接通信。该应用程序能够异步检索必要的数据并单独刷新用户界面的各个块。许多 AJAX 框架都是根据此关键行为设计的,它们包含不同数量的语法修饰、或大或小的功能集以及丰富或简单的用户界面小组件系列。ASP.NET AJAX 就是其中的一种框架。设计和构建真正的 AJAX 应用程序并非一项容易的任务,如果缺少合适的编程工具,甚至会更加困难。与其他 AJAX 框架相比,ASP.NET AJAX 的优越性体现在它为访问服务器端代码所提供的支持上。从开发人员的角度来看,使用 ASP.NET AJAX 连接到可编写脚本的 Web 或 Windows ® Communication Foundation (WCF) 服务就像孩子玩游戏一样简单。当您引用 URL 时,框架会为您生成 JavaScript 代理类。这做起来轻松而有效。代理类隐藏 XMLHttpRequest 细节、序列化和反序列化、数据格式以及 HTTP 打包的所有细节。您只需异步调用一个方法,即可获得自身具备属性集的纯数据传输对象 (DTO)。但是,ASP.NET AJAX 并未提供这么有效率且可在浏览器中使用下载数据的机制。当然,这会很快得到改善,但是现在 ASP.NET AJAX 提供的客户端 UI 模型确实不如服务器端服务模型那么丰富。那么目前如何才能改进 ASP.NET AJAX 纯 Web 应用程序的 UI 模型呢?上个月,我介绍了单页界面 (SPI) 的概念并提供了几个可用于构建 SPI 页的简单模式。本月,我将讨论客户端模板和数据绑定。
典型的 AJAX 功能实时检索股市报价的应用程序是说明 AJAX 方法强大功能的典型示例。如今,提供此功能的非 AJAX 网站要么使用中继刷新标记,要么使用基于插件的方法(如 Flash、Silverlight™ 或 ActiveX ® 控件)。通常,首先将要显示的股票价值列表绑定到服务器端网格控件上,然后在下一次回发或页面请求过程中将该网格与页面其余部分一同刷新。使用部分呈现可以快速改善此类页面。让我们了解一下如何操作。 图 1 中的标记将网格控件封装在一个 UpdatePanel 控件中,Timer 控件会定期刷新该控件。图 1 实时股市报价页面的 UI<asp:UpdatePanel runat="server" ID="UpdatePanel1" UpdateMode="Conditional"> <ContentTemplate> <div style="margin:10px;"> <asp:Label runat="server" ID="Label2" /> <asp:GridView ID="GridView1" runat="server" SkinID="ClassicGrid" AutoGenerateColumns="false" OnRowDataBound="GridView1_RowDataBound"> <Columns> <asp:BoundField DataField="Symbol" HeaderText="SYMBOL" /> <asp:BoundField DataField="Quote" HeaderText="LAST" > <ItemStyle HorizontalAlign="Right" /> </asp:BoundField> <asp:BoundField DataField="Change" HeaderText="CHANGE" > <ItemStyle HorizontalAlign="Right" /> </asp:BoundField> </Columns> </asp:GridView> <small><asp:Label runat="server" ID="Label1" /></small> </div> </ContentTemplate> <Triggers> <asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" /> </Triggers> </asp:UpdatePanel> <asp:Timer runat="server" id="Timer1" Enabled="true" Interval="600000" OnTick="Timer1_Tick" />网格控件被绑定到对象集合中,其中的对象至少具有三个属性:Symbol、Quote 和 Change。网格内容每 10 分钟刷新一次。ASP.NET AJAX Timer 控件会激活客户端计时器,并在间隔结束时触发回发。由于网格控件由 UpdatePanel 封装,因此包含股市报价的网格会独立于页面进行更新。在服务器上,计时器的 Tick 事件会运行下列代码:protected void Timer1_Tick(object sender, EventArgs e) { Label2.Text = "<b>Last update: </b>" + DateTime.Now.ToLocalTime(); GridView1.DataSource = picker.Update(); GridView1.DataBind(); }选取器对象是名为 StockPicker 的自定义帮助程序类的实例。此类包含一个负责检索报价的服务类实例。以下是 StockPicker 类的构造函数:public StockPicker(string stocks) { string provider = ConfigurationManager.AppSettings["StockProvider"]; _service = GetFactory(serviceProvider, stocks); if (_service == null) return; _desc = _service.GetServiceDescription(); }选取器从配置文件读取服务提供程序类的名称并将其实例化。选取器通过 IQuoteService 接口表示的约定与服务类进行通信:interface IQuoteService { StockInfoCollection GetQuotes(); string GetServiceDescription(); }本专栏随附的示例代码使用伪报价服务,该服务只返回随机数字,而不是股市报价。但是,可以轻松配置页面代码以使其使用真正的报价服务。您只需创建实现 IQuoteService 接口的类并连接到实际的股票数据服务即可。在示例代码中,可绑定的数据表示为 StockInfo 对象的集合:public class StockInfo { public string Symbol { get; set; } public string Quote { get; set; } public string Change { get; set; } public string Day { get; set; } public string Time { get; set; } }图 2 显示了运行中的基于此 API 的示例 ASP.NET AJAX 页面。图 2 使用部分呈现的实时报价页(单击图像可查看大图)
部分呈现的限制从功能上讲, 图 2 中的页面会按预期方式运行。如果您对其性能和总体行为感到满意,那最好不过了。但是,这样的页面存在一些弊端,虽然不是很严重,却值得注意。首先,每次更新页面时都会移动大量的数据。数据量对于此类数据相对较少的页面(每 10 分钟刷新一次)来说可能不是问题。如果使用 Fiddler 或 Web Development Helper 进行监视,您会发现下载时的负载大小为 3KB 左右,上传时的负载大小略小一些。同样,如果您对这些数字感到满意,则可以放心使用部分呈现了。但是,如果您查看页面的内部机制,则这些数字会让您颇感失望。页面的视图状态会在每次更新时来回切换。响应包含数据和布局信息。此外,数据已与周围的标记融合在一起,无法对其加以区分。最后,如果页面上还包含其他功能相似的可更新面板,则在同时启动其他部分呈现操作时,计时器可能会停止一段时间。或者,它可能在计时器触发时停止另一挂起操作。最后,尽管部分呈现可以快速掌握和应用,但在需要支持同时发出的异步调用时,部分呈现就不是可采用的理想机制了。让我们了解一下如何使用纯 AJAX 方法重新编写 图 2 中的页面。
重新考虑实时报价页纯 AJAX 方法对实时报价页的设计有什么影响呢?该页面可设置计时器并使用 XMLHttpRequest 调用远程服务。此服务是应用程序后端的一部分,将使用标准 Microsoft ® .NET Framework API 调用财务 Web 服务并获取数据。然后,数据将作为 JavaScript 对象返回到浏览器中。最后,由您决定是否向用户呈现该对象。有何区别?首先,调用的 URL 不是页面本身。页面调用由可编写脚本的 Web 或 WCF 服务支持的 HTTP 端点。由于没有页面生命周期、回发事件和视图状态恢复,网络流量明显减少。在此示例页面中,负载比部分呈现的负载要小 10 倍。从体系结构角度来看,您可以看到两个完全分开的代码块正在运行,即客户端前端和服务器端后端。前者受 JavaScript 支持;后者基于托管代码。ASP.NET AJAX 统一了编程接口和数据类型,这样无论在服务器上定义了哪些编程接口和约定,JavaScript 客户端开发人员都可以看到完全相同的编程接口和约定,因此,ASP.NET AJAX 可谓是此上下文中的一大亮点。JavaScript Object Notation (JSON) 层可确保到达客户端的 DTO 制作从服务器发出的数据的镜像。让我们实验一些代码。新页面包含一个客户端按钮,用户可通过单击该按钮来下载新数据。此处,我使用按钮,而不使用计时器,仅仅是因为前者比较方便:<input type="button" id="btnRefresh" value="Live Quotes" οnclick="_getLiveQuotes()" />然后,JavaScript 处理程序调用包装实时报价 Web 服务上的方法,并获取 StockInfo 对象的集合:<%@ WebService Class="Samples.WebServices.LiveQuoteService" %>图 3 显示了该服务的完整源代码。图 3 实时报价 Web 服务namespace Samples.WebServices { [WebService(Namespace = "http://samples.ajax/")] [ScriptService] public class LiveQuoteService : System.Web.Services.WebService { private static string stocks = ConfigurationManager.AppSettings["StockSymbols"]; private static StockPicker picker = new StockPicker(stocks); public LiveQuoteService() { } [WebMethod] public StockInfoCollection Update() { return picker.Update(); } [WebMethod] public string GetServiceDescription() { return picker.GetServiceDescription(); } } }在部分呈现中,用户界面元素(视图)和核心应用程序逻辑(模型)之间不存在自然分离。所有内容都集中在服务器上,供客户端随时使用。使用纯 AJAX 体系结构,表示层更加智能和丰富,并从物理上与业务层分离。不用说,此类高度分离的体系结构本身就很灵活,并且易于测试。此外,您还可以在其他位置缓存数据,如 图 4 所示。图 4 AJAX 体系结构中可能的缓存级别(单击图像可查看大图)可以将数据缓存到服务器上,由 (Web) 服务前端和实现转交。此外,数据也可由 HTTP 代理缓存,甚至在浏览器中执行的 JavaScript 程序代码缓存。更重要的是,您没有缓存标记;您将缓存的是可以根据运行时条件随时进行检查的可用数据(对象或 JSON 字符串)。将呈现逻辑移动到客户端之所以非常有益,也是因为这样可以减轻繁忙服务器的负担。如果服务器只需返回较小的数据块,而无需执行包括标记生成和视图状态处理的完整页面生命周期,则它可以节省 CPU 周期和内存。在客户端使用 ASP.NET AJAX 与远程服务进行交互并将实时数据返回到浏览器中,同样非常简单。例如,下列代码可调用远程方法:function _getLiveQuotes() { Samples.WebServices.LiveQuoteService.Update(onDataAvailable); } function onDataAvailable(results) { // Update the UI }ASP.NET AJAX 为您提供了与服务器端服务同名的 JavaScript 代理对象和大量静态方法。在上述代码段中,Update 方法最终会提取 JavaScript 文档类型定义 (DTD) 数组并将其传递到最终负责更新用户界面的回调函数中。代码中名为 results 的变量包含来自服务的响应 — 也就是您需要绑定到网格的数据。遗憾的是,存在基于服务器的 ASP.NET 方法,但不存在客户端 GridView 控件。您需要某种形式的客户端数据绑定,最好包含一些模板支持。目前,ASP.NET AJAX 在这方面并没有多大的用处。有关本主题的详细说明(由 Microsoft ASP.NET 团队直接提供),请参阅侧栏文章“深入分析:ASP.NET AJAX”。最近,某些主要 ASP.NET 控件供应商开始为服务器控件提供等效的客户端对象模型。如果使用这些库中的任何库,最终都会获得一个 JavaScript 对象,该对象可模拟服务器端控件,并且包含客户端数据源属性和 DataBind 方法。ASP.NET AJAX 为您提供了从客户端调用 Web 服务所需的基本框架,但是它未提供针对 AJAX 设计的完整 UI 框架,并且也不包括客户端数据绑定和模板。有关从 UI 角度对各种 AJAX 方法进行介绍的有趣资源,可以查看 Miljan Braticevic 发布的文章,网址为 go.microsoft.com/fwlink/?LinkID=116054。
ASP.NET AJAX 中的客户端模板您是否记得“Atlas”?在早期版本(后来成为 ASP.NET AJAX Extensions)中,曾经尝试过提供客户端数据绑定和模板。事实上,这确实是一个很好的尝试。但是,由于某种原因,这一尝试并未在 ASP.NET 的最终版本中得以实现,然后被推送到 ASP.NET Futures 库中了。基本上,旧的 Atlas 绑定模型基于 HTML 模板以及用于表示标记元素和数据字段之间的绑定的声明性语法。然后,模板和绑定在客户端控件(列表视图控件)中连接在一起。通过 XML 进行配置后,Atlas 列表视图控件将会对它检索到的所有信息进行大量的分析工作,并将行为、控件和绑定附加到标记的各个部分。让我们从这最后一点分析构建您自己的较小但有效的数据绑定和模板框架的方式。请注意,最终输出内容必须是 HTML 标记的字符串。最初输入内容是具有一些公共属性的对象集合。您的任务是构建满足用户期望的 HTML 代码段。通常,会首先循环处理绑定集合并将模板的文本与对象的属性值连接在一起。此方法有什么问题?实际上,此方法并没有任何问题。事实上,我想不到有任何不同的方法不是这样做的。这意味着您完全可以创建抽象化该基本例程的框架。在 AJAX 模式目录中(请访问 www.ajaxpatterns.org),定义客户端模板模型的主要步骤以浏览器端模板模式 (BST) 名义进行。
浏览器端模板模式BST 模式的目标是将用于生成数据视图的代码与数据本身分离。长期以来,如何分离视图和数据一直是软件系统中的一大难题,该难题在模型视图控制器 (MVC) 模式的任何变体中都有其标准的解决方案。当 MVC 和 BST 等显示模式完全不互相排斥时,您就可以认为 BST 本身与 MVC 相似,其中不涉及控制器的概念,而是通过实际分离浏览器和服务器将视图和模型分开。 图 5 显示了一个关系图,用于说明 BST 中涉及的步骤。用户触发远程调用,以将某些数据下载到客户端。数据由实例化新型组件(标记生成器)的 JavaScript 回调托管。图 5 运行中的浏览器端模板(单击图像可查看大图)标记生成器基于对页面文档对象模型 (DOM) 中一个或多个 HTML 模板的引用以及下载的数据返回 HTML 字符串。最终,回调在页面的 DOM 中插入该字符串。现在让我们了解一些代码。在此实现中,BST 的核心是 JavaScript MarkupBuilder 类,该类最多可接受三种 HTML 模板,即页眉模板、页脚模板和项目模板。您可以直接从 DOM 引用这些模板,也可以将它们指定为纯字符串文字:function pageLoad() { if (builder === null) { builder = new Samples.MarkupBuilder(); builder.loadHeader($get("header")); builder.loadFooter($get("footer")); builder.loadItemTemplate($get("item")); } }使用不可见的 DIV,可以直接将 HTML 模板嵌入到页面中。但是,仅在块中的标记格式正确时,才可以这样做。更好的办法是将模板作为 XML 数据岛嵌入,如下显示:<xml id="item"> <tr style="background-color:#F0FAFF;"> <td align="left">#Symbol</td> <td align="right">#Quote</td> <td align="right">#Change</td> </tr> </xml>您可以看到,模板是一个使用自定义表示法引用绑定字段的 HTML 块。在这种情况下,我使用 #PropertyName 表达式指示绑定值的占位符。当数据可用时,您只需调用标记生成器上的 bind 方法即可:function _getLiveQuotes() { Samples.WebServices.LiveQuoteService.Update(onDataAvailable); } function onDataAvailable(results) { var temp = builder.bind(results); $get("grid").innerHTML = temp; }显然,在本示例中,名为 grid 的元素是最终输出内容的占位符。MarkupBuilder 类基于 Microsoft AJAX 客户端库。 图 6 显示了其完整的源代码。图 6 MarkupBuilder 类___FCKpd___13在内部,标记生成器使用 Sys.StringBuilder 对象组成 HTML 字符串。它首先添加页眉模板(如果有),然后继续循环处理绑定数据。最后,添加页脚模板。在此实现中,页眉模板和页脚模板不是数据绑定的模板。(图 7 显示了使用标记生成器的页面。)图 7 使用标记生成器的新页面(单击图像可查看大图)
其他注意事项将模板指定为纯字符串与将其指定为嵌入式 DOM 子树或 XML 数据岛是否存在区别?从标记生成器的角度来看,这并不重要,它所需要的仅仅是一个要追加到字符串生成器缓冲区的字符串。从开发人员的角度来看,使用 DOM 模板可能是更好的选择,因为您可以更轻松地验证模板,还可以从 URL 下载该模板。此处提供的解决方案仅适用于数据集合,它遵循用于客户端模板的其他自由框架使用的准则。我希望明确提出的一个框架是 PrototypeJS。要获取详细信息、示例和源代码,请访问 prototypejs.org/api/template。是否存在可以改进的方面?当然!请注意,本专栏中提供的代码仅实现用户希望通过浏览器端模板和绑定引擎获得的最小功能集。从 图 7 中可以看出,当前实现不包括用来区分上涨和下跌股票价格的彩色文字,但 图 2 中包括彩色文字。在 图 2 的代码中,我处理了 GridView 控件的 RowDataBound 事件并直接修改了相关单元格的样式:___FCKpd___14在部分呈现解决方案中,此样式在服务器上进行设置,因此可以利用服务器数据绑定控件的丰富编程模型。在客户端解决方案中,您需要可接受函数代理的更复杂的标记生成器,该函数代理在绑定数据追加到缓冲区之前设置所有绑定数据的样式。生成图 2 和图 7 的代码之间还存在一个更重要的区别,但在打印的图片中看不到。要找出此区别,请尝试运行这两个页面并单击看到的其他按钮(“Weather Forecasts”和“Start Some Task”)。 图 7 中描述的页面在检索股市报价时不会停止其他任务,并且用户通过单击来执行其他任务时不会中止服务调用。重点是什么?请记住,对于部分呈现,在每个会话中一次只能有一个未完成的请求,这与正常的非 AJAX 页面一样。当然,如果您直接通过 XMLHttpRequest 对同时产生的调用进行控制,则可以同时存在任意数量的调用。此外,如果您直接进行服务调用,则不会看到视图状态,也不必担心使其在多个部分呈现调用中保持一致的问题(这正是部分呈现实现阻止您执行并发操作的真正原因)。
HTML 消息模式在本专栏的最后,我将简要介绍一下另外一种模式 — HTML 消息模式,这种模式确实值得在将来的专栏中给予更多重视。HTML 消息模式的目标是使服务器生成要在浏览器中显示的 HTML 标记块。您可以看到,此模式介于部分呈现和其他用于构建真正 AJAX 应用程序的模型之间。如何使用此模式?一种可行的实现方法是,调用远程 URL(服务或临时 HTTP 处理程序)并接收准备显示的 HTML 代码段。请注意,它生成的流量多于纯服务调用生成的流量,但仍少于部分呈现生成的流量。在服务器上,您可以使用服务器控件的新实例(没有视图状态)组合所需的任何输出,并根据需要设置样式。我还会在将来的专栏中介绍此模式。同时,请记住,使用 ASP.NET AJAX 时,您需要编程工具来定义 AJAX 服务层并从客户端浏览器中调用它。此外,您需要功能强大的工具来有效处理客户端上的数据,如基于 JavaScript 的数据绑定和模板。您在查看我在此专栏中提供的 BST AJAX 模式的示例实现,并将其与基于部分呈现的解决方案进行比较时,必然会与我得出相同的结论。
请将您想向 Dino 咨询的问题和提出的意见发送至 cutting@microsoft.com。
Dino Esposito 是《Programming ASP.NET 3.5 Core Reference》的作者 。Dino 定居于意大利,经常在世界各地的业内活动中发表演讲。您可加入他的博客,网址为 weblogs.asp.net/despos。generate, loadHeader: Samples$MarkupBuilder$loadHeader, loadFooter: Samples$MarkupBuilder$loadFooter, loadItemTemplate: Samples$MarkupBuilder$loadItemTemplate } Samples.MarkupBuilder.registerClass('Samples.MarkupBuilder');
在内部,标记生成器使用 Sys.StringBuilder 对象组成 HTML 字符串。它首先添加页眉模板(如果有),然后继续循环处理绑定数据。最后,添加页脚模板。在此实现中,页眉模板和页脚模板不是数据绑定的模板。(图 7 显示了使用标记生成器的页面。)
图 7
使用标记生成器的新页面(单击图像可查看大图)
其他注意事项
将模板指定为纯字符串与将其指定为嵌入式 DOM 子树或 XML 数据岛是否存在区别?从标记生成器的角度来看,这并不重要,它所需要的仅仅是一个要追加到字符串生成器缓冲区的字符串。从开发人员的角度来看,使用 DOM 模板可能是更好的选择,因为您可以更轻松地验证模板,还可以从 URL 下载该模板。
此处提供的解决方案仅适用于数据集合,它遵循用于客户端模板的其他自由框架使用的准则。我希望明确提出的一个框架是 PrototypeJS。要获取详细信息、示例和源代码,请访问
prototypejs.org/api/template。
是否存在可以改进的方面?当然!请注意,本专栏中提供的代码仅实现用户希望通过浏览器端模板和绑定引擎获得的最小功能集。
从
图 7 中可以看出,当前实现不包括用来区分上涨和下跌股票价格的彩色文字,但
图 2 中包括彩色文字。在
图 2 的代码中,我处理了 GridView 控件的 RowDataBound 事件并直接修改了相关单元格的样式:
___FCKpd___14
在部分呈现解决方案中,此样式在服务器上进行设置,因此可以利用服务器数据绑定控件的丰富编程模型。在客户端解决方案中,您需要可接受函数代理的更复杂的标记生成器,该函数代理在绑定数据追加到缓冲区之前设置所有绑定数据的样式。
生成图
2 和图
7 的代码之间还存在一个更重要的区别,但在打印的图片中看不到。要找出此区别,请尝试运行这两个页面并单击看到的其他按钮(“Weather Forecasts”和“Start Some Task”)。
图 7 中描述的页面在检索股市报价时不会停止其他任务,并且用户通过单击来执行其他任务时不会中止服务调用。
重点是什么?请记住,对于部分呈现,在每个会话中一次只能有一个未完成的请求,这与正常的非 AJAX 页面一样。当然,如果您直接通过 XMLHttpRequest 对同时产生的调用进行控制,则可以同时存在任意数量的调用。此外,如果您直接进行服务调用,则不会看到视图状态,也不必担心使其在多个部分呈现调用中保持一致的问题(这正是部分呈现实现阻止您执行并发操作的真正原因)。
HTML 消息模式
在本专栏的最后,我将简要介绍一下另外一种模式 — HTML 消息模式,这种模式确实值得在将来的专栏中给予更多重视。HTML 消息模式的目标是使服务器生成要在浏览器中显示的 HTML 标记块。您可以看到,此模式介于部分呈现和其他用于构建真正 AJAX 应用程序的模型之间。
如何使用此模式?一种可行的实现方法是,调用远程 URL(服务或临时 HTTP 处理程序)并接收准备显示的 HTML 代码段。请注意,它生成的流量多于纯服务调用生成的流量,但仍少于部分呈现生成的流量。
在服务器上,您可以使用服务器控件的新实例(没有视图状态)组合所需的任何输出,并根据需要设置样式。我还会在将来的专栏中介绍此模式。
同时,请记住,使用 ASP.NET AJAX 时,您需要编程工具来定义 AJAX 服务层并从客户端浏览器中调用它。此外,您需要功能强大的工具来有效处理客户端上的数据,如基于 JavaScript 的数据绑定和模板。
您在查看我在此专栏中提供的 BST AJAX 模式的示例实现,并将其与基于部分呈现的解决方案进行比较时,必然会与我得出相同的结论。
请将您想向 Dino 咨询的问题和提出的意见发送至
cutting@microsoft.com。
Dino Esposito 是《Programming ASP.NET 3.5 Core Reference》的作者
。Dino 定居于意大利,经常在世界各地的业内活动中发表演讲。您可加入他的博客,网址为 weblogs.asp.net/despos。