领先技术

改进 ASP.NET MVC 的上下文相关进度栏

Dino Esposito

Dino Esposito
在网络世界里,"进度栏"一词意味着太多不同的东西,对不同的人。有时是指静态文本只是表明,一些操作正在发生的地方。文本为用户提供基本的反馈,并且基本上是邀请她只是放松和等待。有时,进度条显示一个简单的动画,同时向用户发送相同的消息 — 请稍候 — 其中至少集中用户的注意,因为用户倾向于采用移动元素。一个典型的动画显示无限期地绕着一个圆形或线性路径的图形元素。当移动元素到达路径的末尾时,动画开始,并一直运行,直到基础操作终止,并以编程方式停止动画。这是相当普遍的模式上,例如,大多数航空公司的 Web 站点。

上个月,我介绍了一个基本的 ASP 版本。NET MVC 框架 — SimpleProgress 框架 — 允许您设置一个真正上下文相关进度栏的迅速及有效地 (msdn.microsoft.com/magazine/hh580729)。上下文相关的是也许只是哪一个进度栏,应该是。那就是,它应该逐步介绍了正在进行的操作的状态的用户界面元素。逐步显示可以是简单的消息的序列,或者是更令人信服 — 例如,一个仪表。

在这篇文章,我会展示如何通过添加取消功能增强进度栏。换言之,如果用户交互界面,并取消操作,框架将直接经理的日常操作的中断任何正在进行的工作。

取消正在进行的任务

取消正在进行服务器端任务从客户端浏览器中的不是微不足道的操作。不要被蒙骗了非常基本的例子,你就会发现,只是中止客户端请求,假装一切也清除服务器上的。

当你触发服务器操作,通过 AJAX 时,套接字连接到一个远程终结点的您的浏览器打开。此连接保持打开状态,等待完成,并返回一个响应请求。诀窍我讨论了上个月一个上下文相关进度栏设置用于请求的平行流检查第二个控制器的方法负责返回操作的状态 — 排序的邮箱,可以使用客户端和服务器进行通信。

现在假设有一个中止按钮,可以让用户将取消当前的服务器操作。这将需要什么样的代码?至少,您想要中止 AJAX 请求。如果服务器操作已经开始使用 jQuery AJAX API,您可以执行以下各项:

xhr.abort();

Xhr 变量是对调用中所使用的客户端对象的引用。 Jquery,直接由 $就函数返回此引用。 图 1 显示了从上月的代码中添加一个新的进展框架 (重命名进度栏) 摘自中止方法。

图 1 添加中止进展框架的功能

var ProgressBar = function () {
  var that = {};
  // Store the XHR object being used.
that._xhr = null;// Get the user-defined callback that runs after
// aborting the call.
that._taskAbortedCallback = null;
...// Set progress callbacks.
that.callback = function (userCallback, completedCallback,
                          abortedCallback) {
  that._userDefinedProgressCallback = userCallback;
  that._taskCompletedCallback = completedCallback;
  that._taskAbortedCallback = abortedCallback;
  return this;
};
// Abort function.
that.abort = function () {
  if (_xhr !== null)
       xhr.abort();
};
...
// Invoke the URL and monitor its progress.
that.start = function (url, progressUrl) {
  that._taskId = that.createTaskId();
  that._progressUrl = progressUrl;
  // Place the AJAX call.
xhr = $.ajax({
      url: url,
      cache: false,
      headers: { 'X-ProgressBar-TaskId': that._taskId },
      complete: function () {
        if (_xhr.status != 0) return;
        if (that._taskAbortedCallback != null)
            that._taskAbortedCallback();
        that.end();
      },
      success: function (data) {
        if (that._taskCompletedCallback != null)
            that._taskCompletedCallback(data);
        that.end();
            }
  });
  // Start the progress callback (if any set).
if (that._userDefinedProgressCallback == null)
      return this;
  that._timerId = window.setTimeout(
    that._internalProgressCallback,
    that._interval);
};
  return that;
}

正如您所看到的新 abort 方法不会大大超出调用中止内部客户端对象上。 中止正在进行的 AJAX 调用仍触发完整事件对 AJAX 经理,不过,虽然没有任何成功或错误的函数。 若要检测用户是否被中止操作,您将处理程序附加的完成,并检查客户端对象的状态属性 — 它将为 0,如果操作被中止。 完整的处理程序,然后执行任何清理操作是必需的 — 例如停止计时器。 图 2 显示的用户才可以停止远程操作会非常不错的界面。

Cancelable AJAX Operations
图 2 可取消 AJAX 操作

通知服务器

在好的用户界面,在图 2 并不一定能够保证服务器端操作已停止作为该请求的用户和应用程序的反馈,似乎证明。 对客户端调用 abort 只是关闭的套接字的连接到服务器的浏览器。 另一种方式,提出调用 abort 起始地址,你只是说你不再有兴趣接收服务器的方法可能会生成任何响应。 没有什么真正保证服务器接收和承认则中止请求 ; 更可能的是,服务器将继续处理该请求,而不管的浏览器是否正在侦听的响应。 虽然这方面可能因不同的平台和服务器的不同而各异,但却要考虑严格取决于您的应用程序的另一个方面。 如果请求触发异步操作或长时间运行的操作,您可以停止它吗? 事实是请求的,没有任何可靠、 自动的方法来停止处理 ; 你要构建您自己的框架和写入您服务器的方法是可中断。 让我们将进展框架,然后扩展。

扩展的进展框架

在服务器端管理器组件是框架的控制器使用的一部分。 控制器方法发布消息的客户端进度栏和接收通知,从 UI 一直到停止处理下列接口上调用的方法:

public interface IProgressManager
{
    void SetCompleted(String taskId, String format, params Object[] args);
    void SetCompleted(String taskId, Int32 percentage);
    void SetCompleted(String taskId, String step);
    String GetStatus(String taskId);
    void RequestTermination(String taskId);
    Boolean ShouldTerminate(String taskId);
}

与上个月提交的代码相比,有几个额外的方法 — ShouldTerminate,RequestTermination,该客户端会打电话要求终止,以及哪些操作方法将调用来看看是否他们应该停止并回滚。

每个进展经理工程数据提供程序挂起任务的地位的顶部,每个标识生成客户端 id。 默认的数据提供程序在源代码中使用 ASP。净缓存存储任务的状态。 它创建一个词条,每个任务和存储对象的类型了任务状态,内部的条目,如下所示:

public class TaskStatus
{
  public TaskStatus(String status) : this (status, false)
  {
  }
  public TaskStatus(String status, Boolean aborted)
  {
    Aborted = aborted;
    Status = status;
  }
  public String Status { get; set; }
  public Boolean Aborted { get; set; }
}

当控制器的方法调用 SetCompleted 时,它最终在基础存储区中保存该任务的状态消息。 下一步之前,该控制器方法将检查是否有需要中止。

放到一起:服务器

让我们看一下如何才能创建控制器的多步、 可监视和中断的操作方法。 你开始与示例控制器类继承的 ProgressBarController:

public class TaskController : ProgressBarController
{  public String BookFlight(String from, String to)
  {
    ...
}
}

抽样方法返回一个字符串的简单 ; 它可以是局部视图或 JSON 或任何其他您需要它。

基类,示图 3,是一大堆的常用方法赋予的最后的控制器的简单方法。

注意,尤其是,地位和中止的方法定义一个公共和标准的 API,jQuery 客户端调用的查询的当前状态,并要求终止。 通过使用一个基类,可以避免不得不一次次地重写代码。

图 3 的进度条超级类

public class ProgressBarController : Controller
{
  protected readonly ProgressManager ProgressManager;
  public ProgressBarController()
  {
    ProgressManager = new ProgressManager();
  }
  public String GetTaskId()
  {
    // Get the header with the task ID.
var id = Request.Headers[ProgressManager.HeaderNameTaskId];
    return id ??
String.Empty;
  }
  public String Status()
  {
    var taskId = GetTaskId();
    return ProgressManager.GetStatus(taskId);
  }
  public void Abort()
  {
    var taskId = GetTaskId();
    ProgressManager.RequestTermination(taskId);
  }
}

图 4 显示控制器的方法,要从客户端监视需要的模式。

图 4 抽调控制器使用进展框架的方法

public String BookFlight(String from, String to)
{
  var taskId = GetTaskId();
  // Book first leg
  ProgressManager.SetCompleted(taskId,
    "Booking flight: {0}-{1} ...", from, to);
  Thread.Sleep(4000);
  if (ProgressManager.ShouldTerminate(taskId))
  {
    // Compensate here
    //
    return String.Format("One flight booked and then canceled");
  }
  // Book return flight
  ProgressManager.SetCompleted(taskId,     "Booking flight: {0}-{1} ...", to, from));
  Thread.Sleep(4000);
  if (ProgressManager.ShouldTerminate(taskId))
  {
    // Compensate here
    //
    return String.Format("Two flights booked and then canceled");
  }
  // Book return
  ProgressManager.SetCompleted(taskId,     "Paying for the flight ...", taskId));
  Thread.Sleep(5000);
  if (ProgressManager.ShouldTerminate(taskId))
  {
    // Compensate here
    //
    return String.Format("Payment canceled.
No flights booked.");
  }
  // Some return value
  return "Flight booked successfully";
}

在客户端上生成的任务 ID 并将其传输到服务器通过 HTTP 标头。 GetTaskId 方法保护控制器开发人员不必知道这些详细信息的侵扰。 控制器的方法逐段完成其工作,并调用 SetCompleted 每次它来实现重大工作的一部分。 它指示使用字符串,这可能是一个百分比,以及一条状态消息所做的工作。 定期,该控制器方法将检查是否已收到请求终止。 如果出现这种情况,它就可能回滚或补偿,然后返回。

放到一起:客户端

在客户端,您需要链接进展框架 Java­脚本 API 和 jQuery 库:

<script src="@Url.Content("~/Scripts/progressbar-fx.js")"
        type="text/javascript"></script>

每个列出的方法将通过 AJAX 调用,因此将由客户端的事件触发 — 例如,单击按钮,创建的标记所示图 5

图 5 标记触发可监视和中断的行动

<fieldset>
  <legend>Book a flight...</legend>
  <input id="buttonStart" type="button" value="Book a flight..." />
  <hr />
  <div id="progressbar_container">
    <span id="progressbar2"></span>
    <input id="buttonAbort" type="button"
      value="Abort flight booking"
      disabled="disabled" />
  </div>   
</fieldset>
Click handlers are attached unobtrusively when the page is loaded:
<script type="text/javascript">
  var progressbar;
  $(document).ready(function () {
    $("#buttonStart").bind("click", buttonStartHandler);
    $("#buttonAbort").bind("click", buttonAbortHandler);
  });
</script>

图 6 显示的 JavaScript 的启动和终止远程操作以及相应地更新用户界面。

图 6 JavaScript 代码示例视图

function buttonStartHandler() {
  updateStatusProgressBar ();
  progressbar = new ProgressBar();
  progressbar.setInterval(600)
             .callback(function (status) {
                             $("#progressbar").text(status); },
                       function (response) {
                             $("#progressbar").text(response);
                             updateStatusProgressBar(); },
                       function () {
                             $("#progressbar").text("");
                             updateStatusProgressBar(); })
             .start("/task/bookflight?from=Rome&to=NewYork",
                    "/task/status",
                    "/task/abort");
}
function buttonAbortHandler() {
    progressbar.abort();
}
function updateStatusProgressBar () {
    $("#buttonStart").toggleDisabled();
    $("#buttonAbort").toggleDisabled();
}

进度栏 JavaScript 对象包括三种主要的方法。经验之谈方法指定两个连续检查状态更新之间的时间间隔。传递该值以毫秒为单位)。回调方法设置更新状态和更新 UI 操作成功完成时,或当操作已被用户中止回调函数的一群。Start 方法开始操作。它将采取三个 Url:要运行的方法的终结点和终结点的方法被调用到捕获状态更新并中止挂起的操作。

正如您所看到的 Url 是相对的并表示窗体/控制器/方法中。当然,您可以更改方法状态的名称和中止,无论你是喜欢 — 只要这种方法存在作为公众的终结点。保证的状态和中止方法都存在,如果您从您的控制器类 ProgressBarController。图 7 行动显示示例应用程序。虽然在 Ui 图 2图 7 看起来一样,基本代码和行为是真的不同。

The Framework in Action
图 7 中行动框架

取得进展

AJAX 提供工具,轮询服务器并问什么。你要建立您自己的框架,如果您希望它能提供可监视和中断的方法。应当指出的是在一个真实的例子,操作方法本身可能调用其他异步服务。这样的代码可以是复杂的。幸运的是,编写异步代码应该会变得简洁在即将到来的 ASP。NET MVC 4。在我的下一篇专栏文章,我将展示另一种方法来实施和监测远程任务基于新的客户端库,从而也使它到 ASP。NET MVC 4 包 — SignalR 图书馆。现在,得到从源代码 archive.msdn.microsoft.com/mag201201CuttingEdge ,让我知道你的想法 !

Dino Esposito是《Programming Microsoft ASP.NET MVC3》(Microsoft Press,2011 年)的作者,同时也是《Microsoft .NET:构建企业应用程序"(微软出版社,2008年)。埃斯波西托在意大利是一个频繁的演讲者,在全球范围内的行业活动。您可以按照他在 Twitter 上 twitter.com/despos

多亏了以下技术专家审查此列: Phil Haack