你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 网络与通信
4.11 使用 ASP.NET AJAX 取消服务器任务
 

一、触发远程任务

远程任务是在服务器上执行的用于响应客户端事件的一段代码。ASP.NET AJAX 客户端页面触发远程任务的方法有以下三种:使得回发由 UpdatePanel 控件管理,在通过本地 Web 服务公开的应用程序后端直接调用一种方法,使用页面方法。很快会有第四种方法:一种 Windows  Communication Foundation (WCF) 服务。

一旦触发了服务器上的某项任务,客户端将不再控制该任务。仅当由任务生成的响应已下载到客户端并经过解析后,客户端页面才能够重新控制操作。使用 PMF,可以动态地读取任务状态,但不存在将数据动态传送给服务器任务的机制。

二、取消任务的简便方法

使用 ASP.NET AJAX 取消远程服务非常简单,但是存在以下两个限制。首先,该任务必须已通过 UpdatePanel 启动。其次,服务器上不需要任何额外工作来补偿任务的突然中断。下面是基于 UpdatePanel 的页面源代码:

<html xmlns=”http://www.w3.org/1999/xhtml” >

<head runat=”server”>

    <title>Canceling tasks</title>

    <style type=”text/css”>

        #UpdateProgress1  {

            width: 270px; background-color: #ffff99; height:120px;

            top: 40%; left: 35%; position: absolute;

            border: solid 1px black;

        }

        #ProgressTemplate1  {

            font-size: 9pt; color: navy; font-family: verdana;

        }

    </style>

</head>

<script language=”javascript” type=”text/javascript”>

function abortTask()  {

    var obj = Sys.WebForms.PageRequestManager.getInstance();

    if (obj.get_isInAsyncPostBack())

        obj.abortPostBack();

}

</script>

<body>

<form id=”form1” runat=”server”>

    <asp:ScriptManager ID=”ScriptManager1” runat=”server” />

    <asp:UpdatePanel ID=”UpdatePanel1” runat=”server”

            UpdateMode=”Conditional”>

        <ContentTemplate>

            <asp:Button runat=”server” ID=”Button1” Text=”Start Task ...”

                onclick=”Button1_Click” />

            <hr />

            <asp:Label runat=”server” ID=”Label1” /><br />

        </ContentTemplate>

    </asp:UpdatePanel>

    <hr />

    <asp:UpdateProgress runat=”server” ID=”UpdateProgress1”>

        <ProgressTemplate>

            <div ID=”ProgressTemplate1”><p style=”margin:5px;”>

                <img alt=”” src=”Images/indicator.gif”

                    align=”left” />&nbsp;&nbsp;

                <span id=”Msg”>Your request has been submitted and

                    it may take a while to complete.

                <br /><br />Please, wait ... </span>

                <p align=”center”>

                <input type=”button” value=”Cancel”

                    onclick=”abortTask()” /></p>

            </div></p>

        </ProgressTemplate>

    </asp:UpdateProgress>

</form></body></html>

在该页面中,会弹出带有取消按钮的进度模板,如1所示。单击该按钮可以取消操作。




1   带有取消按钮的进度模板

从上面代码中的abortTask函数可以看出,进度模板包含一个绑定到JavaScript代码的客户端按钮。此函数的首要任务是检索页面请求管理器。进行页面初始化时,页面请求管理器会为窗体的提交事件注册一个处理程序。这样,每次响应页面时,都会调用请求管理器。此时,请求管理器会生成请求主体的副本,并通过当前的HTTP执行器(默认指的是常见的 XMLHttpRequest 对象)运行该副本。

页面请求管理器设置部分呈现的事件模型,并跟踪正在执行的操作。如果存在任何挂起的操作,则Boolean属性isInAsyncPostBack将返回true

当用户单击1中所示的取消按钮时,页面请求管理器将通过其abortPostBack方法中止当前请求。页面请求管理器是一个独立对象,即所有调用都只能传递给一个实例。此情形的原因与部分呈现机制紧密相关。部分呈现由发送页面请求组成,包括在服务器上的整个常规处理过程(呈现阶段除外)。此外,这意味着视图状态将被发送,并用于重新创建服务器控件的上次已知正常状态。返回信息和状态更改事件是定期触发的,视图状态即根据这些操作进行更新。然后,更新的视图状态会与进行了部分修改的标记一起发送回来。

由于视图状态的关系,需要对来自同一页面的两个异步回发调用进行序列化,并且每次只允许运行一个调用。由于这一原因,页面请求管理器上的abortPostBack方法不必指出要停止哪一请求,因为至多有一个挂起的请求。

三、深入了解abortPostBack方法

让我们进一步了解PageRequestManager 类中的 abortPostBack 方法,示例代码如下。

function Sys$WebForms$PageRequestManager$abortPostBack()

{

    if (!this._processingRequest && this._request)

    {

        this._request.get_executor().abort();

        this._request = null;

    }

}

如果存在挂起的请求,则管理器将指示中止请求的执行器。执行器是从Sys.Net.WebRequestExecutor 继承的一个JavaScript类,负责发送请求和接收响应。在 Microsoft AJAX 客户端库中,只有一个执行器类(Sys.Net.XMLHttpExecutor 类),它使用XMLHttpRequest对象执行请求。当上述代码调用中止方法时,主要是告知 XMLHttpRequest 对象。从另一个角度来讲,它仅指示执行器用来接收响应数据的套接字必须关闭。

现在,假设远程任务在服务器上执行破坏性操作。例如,假设为用户提供了一次机会,使其能够通过单击一个按钮来删除数据库表中的少量记录。通过上述过程尝试取消操作实际上不会停止服务器操作。它所能实现的所有功能就是关闭用来接收确认消息的套接字。PageRequestManager对象上的abortPostBack方法仅仅是一个客户端方法,对服务器中运行的操作不会起到任何作用。

四、设计不间断任务

要使中止请求对服务器操作有效,任务必须是不间断的。换句话说,任务必须定期检查是否存在来自客户端的指示任务退出的说明。

当首次实现 PMF时,框架的客户端和服务器元素共享一个通用数据容器,服务器使用该容器写入关于其进度的数据,客户端使用该容器读取此数据,以更新用户界面。要使得服务器代码接收并处理动态客户端反馈(如单击取消按钮),需要用到一些增强功能。目前,进程服务器 API 基于以下约定:

public interface IProgressMonitor

{

    void SetStatus(int taskID, object message);

    string GetStatus(int taskID);

    bool ShouldTerminate(int taskID);

    void RequestTermination(int taskID);

}

这里添加了两个新方法:ShouldTerminate RequestTermination。前者返回一个 Boolean 值,表明是否应终止正在执行的任务。RequestTermination 方法为希望结束任务的客户端指示 API 中的入口点。调用此方法时,它会在数据容器(ASP.NET 缓存)中创建一个与任务相关的入口,ShouldTerminate 会检查此入口以确定是否请求了中断。

上文中定义的 IProgressMonitor 接口指示服务器上某个应用程序的预期行为。可以在可能使用不同数据容器的各种类中实现该接口。笔者使用名为 InMemoryProgressMonitor ASP.NET 缓存创建了一个示例类,核心代码如下:

public class InMemoryProgressMonitor : IProgressMonitor

{

    public const int MAX_TIME_MINUTES = 5;

    //从任务调用此方法,它将任务的当前状态写入内部数据存储。该状态以对象的形式表示。

    public void SetStatus(int taskID, object message)

    {

        HttpContext.Current.Cache.Insert(

            taskID.ToString(), message, null,

            DateTime.Now.AddMinutes(MAX_TIME_MINUTES),

            Cache.NoSlidingExpiration);

    }

    //从内部数据存储读取指定任务的当前状态,并将其以字符串的形式返回到客户端。

    public string GetStatus(int taskID)

    {

        object o = HttpContext.Current.Cache[taskID.ToString()];

        return o == null ? string.Empty : (string)o;

    }

    //如果客户端发出了终止指定任务的请求,则返回 true

    public bool ShouldTerminate(int taskID)

            {

        string taskResponseID = GetSlotForResponse(taskID);

        return HttpContext.Current.Cache[taskResponseID] != null;

    }

    //在内部数据存储中创建与任务相关的入口,以指示客户端发出了终止请求。

    public void RequestTermination(int taskID)

    {

        string taskResponseID = GetSlotForResponse(taskID);

        HttpContext.Current.Cache.Insert(

            taskResponseID, (object) false, null,

            DateTime.Now.AddMinutes(MAX_TIME_MINUTES),

            Cache.NoSlidingExpiration);

    }

    private string GetSlotForResponse(int taskID)

    {

        return String.Format(“{0}-Response”, taskID);

    }

}

要支持动态中断,相同的任务将定期调用 ShouldTerminate,以便在客户端请求退出时获得通知。下面的代码显示了可监视的不间断任务的典型结构:

public static string ExecuteTask(int taskID)

{

    InMemoryProgressMonitor progMonitor = new InMemoryProgressMonitor();

   if (progMonitor.ShouldTerminate(taskID))

        return “Task aborted--0% done”;

 

    // 第一步

    progMonitor.SetStatus(taskID, “0”);

    DoStep(1);

    if (progMonitor.ShouldTerminate(taskID))

        return “Task aborted--5% done”;

    // 第二步

    progMonitor.SetStatus(taskID, “5”);

    DoStep(2);

    if (progMonitor.ShouldTerminate(taskID))

        return “Task aborted--45% done”;

    // 第三步

    progMonitor.SetStatus(taskID, “45”);

    DoStep(3);

    if (progMonitor.ShouldTerminate(taskID))

        return “Task aborted--69% done”;

    // 最后一步

    progMonitor.SetStatus(taskID, “69”);

    DoStep(4);

    if (progMonitor.ShouldTerminate(taskID))

        return “Task aborted--100% done”;

    return “Task completed at: “ + DateTime.Now.ToString();

}

上面代码中显示的方法用于协调组成远程任务的各个步骤。该任务可以是应用程序的中间层的一部分,也可以作为工作流实现。它在各步骤间必须是相互关联的,以便客户端插入到其中读取状态和请求终止。

五、客户端代码

可以使用页面或Web服务方法(如ExecuteTask方法)启动任务,或在 UpdatePanel区域中运行用于触发远程任务的JavaScript服务器代码。

<asp:UpdatePanel runat=”server” ID=”UpdatePanel1”>

    <ContentTemplate>

        <asp:Button runat=”server” ID=”Button1” Text=”Start Task ...”

             OnClick=”Button1_Click” />

        <hr />

        <asp:Label runat=”server” ID=”Label1” /><br />

    </ContentTemplate>

</asp:UpdatePanel>

Button1_Click 事件处理程序中,定义了远程任务,并使其调用进度监视器对象以及SetStatus ShouldTerminate方法。要突然终止一个远程任务,需要在进度模板中添加一个取消按钮,它可以是 UpdateProgress 控件,也可以是用户定义的一个 <div> 块。此时,取消按钮的单击处理程序不指向页面请求管理器中的 abortPostBack 方法,而是指向客户端进度 API 中自己的中止方法。

<script type=”text/javascript”>

var progressManager = null;

var taskID = null;

function pageLoad() {

   progressManager = new Samples.PMF2.Progress();

}

function abortTask() {

    progressManager.abortTask(taskID);

}

...

</script>

下面让我们来看一下经过修改的客户端进度 API。此 API progress.js 文件中进行编码,因此必须链接到计划使用不间断或可监视任务的每个 ASP.NET AJAX 页面。

<asp:ScriptManager ID=”ScriptManager1” runat=”server”

    EnablePageMethods=”true”>

    <Scripts>

        <asp:ScriptReference path=”random.js” />

        <asp:ScriptReference path=”progress.js” />

    </Scripts>

</asp:ScriptManager>

random.js 文件与 progress.js 相关,定义了一种可生成随机数量任务的方法。要从客户端跟踪远程任务的状态,需要定期轮询服务器。要停止正在执行的任务,或者更确切地说,要发出一个请求以停止任务,需要调用一个服务器方法,该方法是由进度监视器服务器 API 作为应用程序后端的一部分发布的。

// 取消操作

function Samples$PMF2$Progress$abortTask() {

    PageMethods.TerminateTask(_taskID, null, null, null);

}

笔者选择使用页面方法发布此客户端可调用函数。整个解决方案的架构如图2所示。


 


2  双向进度监视器框架

用户单击取消按钮时,会触发一个带外调用以执行TerminateTask方法,此方法是作为页面的后续代码类上的页面方法定义的。TerminateTask方法在内部数据存储(ASP.NET 缓存)中创建一个与任务相关的入口。此入口是按带有“Quit”后缀的任务 ID 命名的。设计为不间断的任务在执行过程中的各个阶段检查此入口。如果找到了该入口,则服务器任务中止,如图3所示。



3  用户单击取消按钮,结束服务器任务

通过此方式实现的任务取消将更有效。如果在 UpdatePanel 刷新过程中仅中止客户端回发,所导致的全部结果将是关闭用于接收响应的客户端套接字。对服务器上运行的代码不会产生任何影响,也不存在以编程方式停止对 Web 服务或页面方法的远程调用的内置方法。在这种情形下,JavaScript 代理类完全隐藏了正被用于推送调用的请求对象。虽然请求对象及其执行器具有中止方法,但在服务方法调用的上下文中找不到对它的引用。

最后,如果需要允许远程任务控制,进度指示器模式是唯一可行的方法。设置并行信道来监视状态,并向正在运行的任务传递更多信息(如退出命令)。这种相同的体系结构允许客户端动态更改参数或请求其他操作。双向进度监视器框架是双工信道,服务器任务及其 JavaScript 客户端可使用该信道交换消息形式的数据。

六、事务

至此,已创建了一个框架用以监视和停止 ASP.NET AJAX 任务。关键需要注意的是,该框架只是通知任务用户请求其终止。如果设计正确,任务会立刻停止并返回。但对于已完成的工作会如何处理呢?

一般情况下,当任务突然中断时,应撤消它所做的所有更改并返回。但进度监视器框架无法实现此功能。不过,如果将远程任务封装在事务中,即可在该任务中断后立即回滚。另一种选择是使用工作流。在这种情形下,将任务封装在TransactionScope活动中,使用 Code 活动设置当前状态并检查是否有终止请求。如果任务必须终止,会引发异常并自动导致事务回滚。并非所有操作都可轻松地自动回滚,一般情况下,可以实现TransactionScope块内部的任务,并安全有效地使用用于实现Transaction界面的所有对象。如果这样做,则所有对象都将相应地回滚或提交。其中每个对象都了解如何撤消其更改。

底线是从客户端监视远程任务的进度,此操作相对简单,不会产生严重的负面影响。PMF在其上增加了一些好的抽象,并提供了一些现成的编程工具。使任务不间断会引发一些其他问题,当任务具有固有的事务语义时尤其如此。编写代码来只通知任务用户请求其终止是游戏中相当简单的一部分。真正复杂的部分在任务实现及其补偿策略中。

七、生成进度条

在本文即将结束时,介绍一下如何使用 JavaScript 轻松生成进度条标记,并使其更易于维护。进度条可以通过构建 HTML 表生成,代码如下:

<table width=”100%”>

  <tr>

    <td>69% done</td>

  </tr>

  <tr>

    <td bgcolor=”blue” width=”69%”>&nbsp;</td>

    <td width=”31%”></td>

  </tr>

</table>

此表包含两行:附带文本和仪表。仪表使用两单元格的行来呈现,其中的单元格已给定背景色和成比例的宽度。

仔细查看上述标记,至少能够识别三个参数:面向用户的消息、要显示的值,以及要对已完成未完成区域使用的颜色。这样就不再生成字符串形式的标记,创建 JavaScript 类会更简洁。Samples.GaugeBar 类的实现实现方法如下:

function Samples$GaugeBar$generateMarkup(text, perc) {

    var builder = new Sys.StringBuilder(“”);

    builder.append(“<table width=’100%’><tr><td colspan=’2’>&nbsp;”);

    builder.append(text);

    builder.append(“</td></tr><tr><td bgcolor=”);

    builder.append(this._doneBackColor);

    builder.append(“ width=’”);

    builder.append(perc + “%’>”);

    builder.append(“&nbsp;</td><td bgcolor=”);

    builder.append(this._todoBackColor);

    builder.append(“ width=’”);

    builder.append(100-perc + “%’>”);

    builder.append(“</td></tr></table>”);

    return builder.toString();

}

该方法使用文本和百分比,返回包含两行的 HTML 表。顶行仅显示文本,底行分为两个单元格,分别带有不同的颜色。

标记字符串是使用JavaScript版本的Microsoft .NET Framework StringBuilder对象构建的。JavaScript StringBuilder 对象是在系统命名空间中定义的,其编程接口类似于.NET Framework 接口。向StringBuilder的内部缓冲区发送文本,然后使用toString方法输出文本。

Samples.GaugeBar类具有一个generateMarkup方法,以及已完成未完成区域的背景色、附带文本的前景色等属性。由于性能方面的原因,此类作为单例来使用。这个类不是很大,但每次需要更新进度条时,仍然不必为其创建新实例。因此,可以为该类定义一个静态实例,并添加一些静态方法和属性:

Samples.GaugeBar.registerClass(‘Samples.GaugeBar’);

Samples.GaugeBar._staticInstance = new Samples.GaugeBar();

Samples.GaugeBar.generateMarkup = function(text, perc) {

   return Samples.GaugeBar._staticInstance.generateMarkup(text, perc);

}

要更改颜色,请执行以下操作:

Samples.GaugeBar.set_DoneBackColor(“#ff00ee”);

Samples.GaugeBar.set_TodoBackColor(“#ffccee”);

同样,可以通过为表的已完成单元格定义开始边框样式,添加美观的 3D 效果,实现代码如下:

if (this._effect3D)

    builder.append(“ style=’border:outset white 2px;’”);

通过创建一个类来公开功能可大大提高 JavaScript 编程的可管理性。Microsoft 客户端AJAX库是一个很大的进步,因为使用此库编写复杂的JavaScript代码会轻松得多。大多数AJAX专业人员可能都同意这一点:要实现强大的 AJAX 编程,必须具备更丰富的 JavaScript功能。

  推荐精品文章

·2024年12月目录 
·2024年11月目录 
·2024年10月目录 
·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089