讲座展示:Live From Redmond: Microsoft AJAX Patterns - Implementing Predictive Fetch with Microsoft ASP.NET 2.0 AJAX Extensions
简介
这次我选择的讲座,是最近在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 Roufus</asp:ListItem>
<asp:ListItem>Rick Roufus</asp:ListItem>
<asp:ListItem>Duane Ludwig</asp: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中响应它的事件。如下:
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。它的作用是能够从客户端得到一个运动员的资料或战绩。如下(省去了许多代码):
FetchData方法
<%@ WebService Language="VB" Class="DataService" %>
Imports System.Web
Imports System.Web.Services
Imports System.Web.Services.Protocols
<WebService(Namespace:="[url]http://tempuri.org/[/url]")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Microsoft.Web.Script.Services.ScriptService()> _
Public Class DataService
Inherits System.Web.Services.WebService
<WebMethod()> _
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方法。如下:
GetDetails方法
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的使用。如下:
使用ScriptManager引入DataService.asmx
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="DataService.asmx" />
</Services>
</asp:ScriptManager>
当用户点击按钮时,我们会将“预加载”的内容显示在下方的DIV中。然后会再次调用GetDetails方法,因为在显示了信息之后,用户一般会花一段时间查看这些信息,我们应该“预测”用户接下来的行为,以进行信息的“预加载”。当然最后会改变按钮上的文本,告诉用户接下来能够查看什么数据。如下:
响应按钮点击事件的代码
function btnMore_
{
document.getElementById('divDetails').innerHTML = detailContent;
GetDetails();
document.getElementById('btnMore').value =
"Get " + currentDetails;
}
现在还有一个问题,比如我们在选择另一个运动员之后,我们并没有进行任何数据的“预加载”,因此我们必须定义一个pageLoad函数。这个函数会被ASP.NET AJAX Client Library在页面被加载时访问。在这个函数中,我们会判断用户是否选择了一个运动员,并进行预加载。如下:
pageLoad函数
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”……:)