简介
这次我选择的讲座,是最近在Live From Redmond系列WebCast中Joe Stagner的讲座“Microsoft AJAX Patterns - Implementing Predictive Fetch with Microsoft ASP.NET 2.0 AJAX Extensions”。Joe Stagner是UI, Tools & Platforms Team的Program Manager。作者在他的Blog上也提供了这个讲座的PPT和演示代码下载(VB.NET编写,不过我想在阅读上应该不会有很大问题)。另外,Microsoft Events也以On-Demand Webcast Events的形式提供了这个讲座的完整视频(不过这个视频从形式到质量都比TechEd和Mix的要差远了)。
在AJAX形式的Web开发中存在着一些常用的编程模式。在这个讲座里,Joe会讲述并且使用ASP.NET 2.0 AJAX Extensions做一个简单的演示,用来说明“Predictive Fetch”模式。
关于Live From Redmond系列 “Live From Redmond”是一系列由微软各个产品部门所设计,面向社区的Live Meeting讲座。这些讲座的演讲者都是来自真正的微软产品组,他们都为某个特定的技术工作。这些讲座都是从他们那里获得各种信息的好机会。
讲座内容
现在已经存在了一系列的有关AJAX的设计模式,或者说是一系列使用AJAX的形式。我们现在来看一下今天要实现的设计模式,这个AJAX模式叫做“Predictive Fetch”。我们现在已经有了增加用户体验,改善UI的方式,那就是更新页面的一部分,而避免对于整张页面进行完整的刷新,页面上其余的内容依旧留在页面上。也就是说,我们现在已经有了一个方法,能够使用JavaScript和服务器交换数据,而这个过程可以不为用户所察觉。那么我们现在就有了一个更加好的想法:用户下一步会做什么?我们需要预测用户下一步最有可能执行的操作,例如,最典型的“Predictive Fetch
”使用场景(不过我们这次不把它作为例子),就是在一篇有许多页的文章里,我们会想:“现在用户在第一页,他有90%的可能会点击下一页”、“如果他已经看到了所有6页中的第3页,那么他会有66%的可能性会查看下一页,而会有33%的可能性会看前一页。”这样,我们就根据UI的当前状况,我们可以预测用户下一步最有可能会作哪些事情,然后对于所需要的数据进行“预获取”。而这时候用户不会察觉到浏览器正在从服务器上获取数据。
我们在这里可以使用UpdatePanel来做到这一点,不过我更喜欢使用JavaScript从头开始开发——不要误会我的意思,UpdatePanel是个神奇的东西——不过有时候我还是比较乐意使用JavaScript进行开发。UpdatePanel的优点在于它非常容易使用,不过它的缺点在于每一次使用UpdatePanel做页面部分刷新时,都会产生一次完整的PostBack,包括所有的ViewState会被传递到服务器端,而在服务器上也会出现一次完整页面生命周期。我们能够很方便地将一个UpdatePanel拖到页面上产生部分刷新的效果,但是如果我们使用自己的Callback就会避免将页面的大量数据被传递到服务器端,也不会在服务器上出现完整的页面生命周期,因为我们很明确的去访问了一个Service Method,这就会高效许多。这也是我们这次演示会使用的方法。
使用AJAX的话我们也必须明白,在这里Page Navigation的含义变了。在大多数AJAX形式的应用中,“前进”和“后退”带来的效果和传统的应用不一样了,比如我父亲——他不会理解到底浏览器的运作方式和使用的技术——有时候他们会被搞糊涂,他们如果想看之前的内容,他们会去点击“后退”按钮。现在的AJAX应用都会使用一些方式去“弥补”这个问题。
在AJAX应用中,还必须考虑的一个话题就是“状态”。如果我在页面中使用了JavaScript获取了数据并且修改了内容,这些内容往往不会被带到服务器端,我们必须设法保留这些状态。一会儿我会演示该如何做到这些——您能够简单地使用Cookie,但是我在示例中将会使用JavaScript来编写一个简单的状态保存机制。
讲座演示
我是一个拳击迷,所我在这里将会展示的网站,它的作用就是查看运动员的状况。如图(点击小图可查看大图):
当我切换下拉框里时,页面会自动地PostBack,以此获得不同的运动员的图片。如图(点击小图可查看大图):
如果您想让它看上去更“AJAX”一些,您可以使用客户端控件然后编写一些客户端代码,不过这对于我们的示例来说并不重要。这里还有一个按钮来获得这个运动员的资料,我已经“预测”您会需要查看这些信息,已经提前获取了。因此在您点击这个按钮时,它们能被很快地显示出来,并且页面没有刷新。如图(点击小图可查看大图):
现在按钮已经变成了“Get Record”,因此我也“提前”获得了这个运动员的战绩。当您点击时,它们也被显示出来了,页面没有刷新和闪烁。如图(点击小图可查看大图):
按钮又变成了“Get Stats”,这样我们就能在运动员的资料和战绩之间来回切换。可能在本地我们无法看出效果,事实上我在每次得到当前的信息后都会“预测”用户会获得下次的信息,因此进行“预加载”。当然,如果您选择其它的运动员也一样。
我们现在看一下应该如何创建这样一个应用。
自然一开始,依旧是要引入ASP.NET AJAX的功能:引入程序集,编辑web.config文件等等。然后在页面里添加一个ScriptManager,再添加一些必要的元素——一个图片控件、一个下拉框、一个按钮以及最后一个用来显示信息的DIV。如下:
<asp:ScriptManager ID="ScriptManager1" runat="server" /> <div style="text-align: center"> <asp:Image ID="FighterPhoto" runat="server" ImageUrl="~/images/blank_fighter.jpg" Height="223px" Width="162px" /> <br /><br /> <asp:DropDownList ID="ddFighterList" runat="server" AutoPostBack="True"> <asp:ListItem Selected="True" Value="None"> Select Athlete ... asp:ListItem> <asp:ListItem>Jeff Roufusasp:ListItem> <asp:ListItem>Rick Roufusasp:ListItem> <asp:ListItem>Duane Ludwigasp:ListItem> asp:DropDownList> <br /><br /> <input id="btnMore" style="width: 171px" type="button" value="Get Stats" language="javascript" onclick="return btnMore_onclick()" /> <br /><br /> <div id="divDetails" style="width: 868px; height: 100px">div> div>
按理来说,我们应该从数据库里得到这些信息,但是在这里我完全使用了静态的信息——我不想让一个简单的演示也要动用数据库。请注意我们这里的按钮不是一个服务器控件,因为我们会对它们进行客户端的开发,进行“Predictive Fetch”,所以我们在这里使用了一个标准的HTML按钮。
如果您对于ASP.NET AJAX有什么意见的话,您可以写Email给我,我将会把这些功能整理后提交给负责所有功能的GPM。比如在1.0版的ASP.NET中,我们将会提供一个可以将程序集放在Bin目录下的解决方案,这样在没有安装ASP.NET AJAX的虚拟主机下也能使用ASP.NET AJAX的功能了。ASP.NET AJAX的正式版将在12月发布,虽然还没有确定一个时间,但是应该可以在12月份发布它的1.0正式版。
接下去我们将为下拉框在Code-Behind中响应它的事件。如下:
Protected Sub ddFighterList_SelectedIndexChanged (ByVal sender As Object, ByVal e As System.EventArgs) Handles ddFighterList.SelectedIndexChanged Select Case (ddFighterList.SelectedValue) Case "Jeff Roufus" FighterPhoto.ImageUrl = "images/Jeff Roufus.jpg" Case "Rick Roufus" FighterPhoto.ImageUrl = "images/Rick Roufus.jpg" Case "Duane Ludwig" FighterPhoto.ImageUrl = "images/Duane Ludwig.jpg" Case Else FighterPhoto.ImageUrl = "images/blank_fighter.jpg" End Select End Sub
然后我们就要开始编写客户端的功能了。再这之前我们还需要编写一个Web Service。它的作用是能够从客户端得到一个运动员的资料或战绩。如下(省去了许多代码):
<%@ WebService Language="VB" Class="DataService" %> Imports System.Web Imports System.Web.Services Imports System.Web.Services.ProtocolsNamespace:="http://tempuri.org/")> _ _ _ Public Class DataService Inherits System.Web.Services.WebService _ Public Function FetchData(ByVal strWho As String, ByVal strWhat As String) As String Dim strDetails As String strDetails = "" Select Case (strWho) Case "Rick Roufus" If strWhat = "Stats" Then strDetails = ... ElseIf strWhat = "Record" Then strDetails = ... End If Case "Jeff Roufus" ... End Select Return strDetails End Function End Class
我们的FetchData方法接受两个参数,第一个参数表示哪个运动员,第二个参数表示哪项信息,这里还是以拼接字符串的形式将数据拼接成HTML。
下面我们将编写代码来管理状态。我们必须知道当前页面中已经加载了哪种数据(currentDetails),然后我们才能够进行“预加载”。然后我们需要保存那些我们已经获取了,但是还没有被显示在页面上的信息(detailContent)。如下:
var currentDetails = "None"; var detailContent = "";
然后我们需要写一个JavaScript函数去向Web Service方法“预加载”数据。我们的Web Service方法有两个参数,一个是“Who”还有一个是“What”。“Who”的话比较简单,我们只需要获得当前下拉框的值即可,不过获得“What”就会相对困难些,我们需要使用一种简单的状态保留的机制,以得知我们需要获取什么样的数据。在这里我们使用了switch分支来判断currentDetails的值。如果是“None”,表示我们还没有获得任何数据,那么我们会去“预加载”这个运动员的“资料”,以此类推。最后我们会调用那个Web Service方法。如下:
function GetDetails() { switch(currentDetails) { case "None": currentDetails = "Stats"; break; case "Stats": currentDetails = "Record"; break; case "Record": currentDetails = "Stats"; break; } fighterDetails = DataService.FetchData( document.getElementById('ddFighterList').value, currentDetails, OnComplete, OnTimeOut, OnError); } function OnComplete(retResult) { detailContent = retResult; } function OnTimeOut(retResult) { alert(retResult); } function OnError(retResult) { alert(retResult); }
自然我们还需要在ScriptManager里引入Web Service的使用。如下:
<asp:ScriptManager ID="ScriptManager1" runat="server"> <Services> <asp:ServiceReference Path="DataService.asmx" /> Services> asp:ScriptManager>
当用户点击按钮时,我们会将“预加载”的内容显示在下方的DIV中。然后会再次调用GetDetails方法,因为在显示了信息之后,用户一般会花一段时间查看这些信息,我们应该“预测”用户接下来的行为,以进行信息的“预加载”。当然最后会改变按钮上的文本,告诉用户接下来能够查看什么数据。如下:
function btnMore_onclick() { document.getElementById('divDetails').innerHTML = detailContent; GetDetails(); document.getElementById('btnMore').value = "Get " + currentDetails; }
现在还有一个问题,比如我们在选择另一个运动员之后,我们并没有进行任何数据的“预加载”,因此我们必须定义一个pageLoad函数。这个函数会被ASP.NET AJAX Client Library在页面被加载时访问。在这个函数中,我们会判断用户是否选择了一个运动员,并进行预加载。如下:
function pageLoad () { if(document.getElementById('ddFighterList').value != "None") { GetDetails(); } }
感想
事实上我必须承认,这次讲座的质量并不高。这次的讲座,立意不错,可惜内容讲解的比较肤浅,还有这个例子举得实在是比较糟糕,过于简单。Live From Redmond其实也应该算是比较有特色的Web Cast了,可惜其质量——至少这次的质量非常的一般。也有可能是刚经过了TechEd Europe Edition的“溺爱”,对于讲座的“口味”也变得挑剔了。下一次我将会准备将讲述一下TechEd Europe中的“DEV370 - AJAX Patterns with ASP.NET AJAX”,相信大家也能够看出个中差距。
另外,我似乎觉得Joe Stagner对于ASP.NET AJAX不怎么熟悉。最近在它的Blog上发了这么一个Post,大意是说他不知道如何设置一个Web Service Call的Timeout,结果从ASP.NET AJAX Team里的一个人那里才知道……我想,园子里已经掌握这点的朋友应该不在少数吧?还有他在上面的例子里,为什么还在使用“document.getElementById”方法,而不是“$get”……:)