共用方式為


技術最前線

加強 ASP.NET MVC 即時線上進度列

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 是作者的"程式設計微軟 ASP。網 MVC3"(微軟出版社,2011年) 並散佈"微軟。網:構建企業應用程式"(微軟出版社,2008年)。埃斯波西托在義大利是一個頻繁的演講者,在全球範圍內的行業活動。您可以按照他在 Twitter 上 twitter.com/despos

多虧了以下技術專家審查此列: Phil Haack