Hello World
Spiga

讲座展示:Live From Redmond: Microsoft AJAX Patterns - Implementing Predictive Fetch with Microsoft ASP.NET 2.0 AJAX Extensions

2006-12-03 23:17 by 老赵, 2111 visits

简介

这次我选择的讲座,是最近在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中响应它的事件。如下:

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.Protocols
<WebService(Namespace:="http://tempuri.org/")> _
<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方法。如下:

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”……:)

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

6 条回复

  1. TerryLee
    *.*.*.*
    链接

    TerryLee 2006-12-03 23:38:00

    老赵辛苦了:)

    // SF

  2. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-12-04 00:32:00

    Predictive Fetch是一个很多人都想到过的模式,问题是“难以量产”。

    暂时来说,要设计一个统一的引擎来做Predictive Fetch是不容易的,因为在每一种情况下prediction都不同。我们可以让设计师手动来config每种情况下的prediction,这样客户端就要有根据config作判断的能力,判断用户当前的操作是否符合config中的prediction,这样说来客户端还是要和服务器端沟通来了解config,搞不好效率还低了。

    如果要更加强大的设计,就应该收集用户的导航习惯,保存到Profile,提供个性化的prediction,这个方案比上面的更复杂。

  3. 木野狐
    *.*.*.*
    链接

    木野狐 2006-12-04 01:14:00

    这个思想是好的,可是在实际应用里到处使用带来的额外开发工作量就太大了,呵呵。

  4. 老赵
    admin
    链接

    老赵 2006-12-04 09:16:00

    @TerryLee
    :)

  5. 老赵
    admin
    链接

    老赵 2006-12-04 09:18:00

    @木野狐
    算是一种方式吧。:)

  6. 老赵
    admin
    链接

    老赵 2006-12-04 09:18:00

    @Cat Chen
    是的,要做好Predictive Fetch需要有数据说话的,所以除了一些常见的用例,要做好Prediction不容易。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我