Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений .NET Framework Расширение контекстно-зависимого индикатора прогресса для ASP.NET MVC RSS

Расширение контекстно-зависимого индикатора прогресса для ASP.NET MVC

Текущий рейтинг: 0 (проголосовало 0)
 Посетителей: 895 | Просмотров: 2823 (сегодня 0)  Шрифт: - +
В мире Web термин «индикатор прогресса» (progress bar) имеет слишком много значений для разных людей. Иногда так называют статический текст, который просто сообщает, что где-то выполняется некая операция. Этот текст обеспечивает базовую обратную связь с пользователем и, по сути, предлагает ему расслабиться и подождать. В каких-то случаях индикатор прогресса в дополнение к тому же тексту отображает простую анимацию, по крайней мере привлекая внимание пользователя. В типичной анимации показывается графический элемент, который бесконечно перемещается по круговому или линейному пути. Когда движущийся элемент достигает конца пути, анимация начинается заново и так продолжается, пока не завершится выполняемая операция. Это довольно распространенный шаблон, например, на большинстве веб-сайтов авиакомпаний.

В прошлый раз я представил вам базовую версию инфраструктуры для ASP.NET MVC — SimpleProgress Framework, — которая позволяет быстро и эффективно создавать настоящий контекстно-зависимый индикатор прогресса (msdn.microsoft.com/magazine/hh580729). Зависимость от контекста — это, вероятно, истинная сущность индикатора прогресса. То есть это должен быть такой элемент UI, который отражает состояние выполняемой операции. Прогресс операции может отображаться очень просто, скажем, как последовательность сообщений, или более наглядно, например как измерительный прибор. В этой статье я покажу, как расширить индикатор прогресса, добавив поддержку отмены операции. Иначе говоря, если пользователь взаимодействует с интерфейсом и отменяет операцию, инфраструктура будет сообщать диспетчеру выполняемой операции о прекращении всей связанной с ней работы.

Отмена выполняемых задач

Отмена из клиентского браузера выполняемой на серверной стороне задачи — операция нетривиальная. Пусть вас не обманывают самые элементарные примеры отмены клиентского запроса, которые вы, возможно, найдете в литературе, и не думайте, будто на сервере произойдет автоматическая очистка всего, что было связано с этим запросом.

Когда вы инициируете серверную операцию через AJAX, открывается сокет, который соединяет ваш браузер с удаленной конечной точкой. Это соединение остается открытым до завершения запроса и возврата ответа. Рассмотренный в прошлой статье прием для установки контекстно-зависимого индикатора прогресса был основан на параллельном потоке запросе, проверявших второй метод контроллера, отвечающий за возврат состояния операции, — этот метод является своего рода почтовым ящиком, через который могут взаимодействовать клиент и сервер.

Теперь допустим, что есть кнопка Abort, позволяющая отменить текущую серверную операцию. Какой код для этого потребуется? Как минимум, вы хотите отменить AJAX-запрос. Если серверная операция была запущена с использованием jQuery AJAX API, вы можете сделать следующее:

xhr.abort();

Переменная xhr — это ссылка на объект XmlHttpRequest, используемый в вызове. В jQuery эта ссылка возвращается напрямую функцией $.ajax. На рис. 1 показан фрагмент инфраструктуры индикации прогресса (переименованный ProgressBar) из кода за прошлый месяц, который добавляет новый метод abort.

Рис. 1. Добавление функциональности отмены в инфраструктуру индикации прогресса

var ProgressBar = function () {
  // Сохраняем используемый объект XHR
  var that = {};
// Получаем пользовательский обратный вызов,
// выполняемый после отмены вызова
that._xhr = null;
that._taskAbortedCallback = null;
...
// Задаем обратные вызовы индикации прогресса
that.callback = function (userCallback, completedCallback,
    abortedCallback) {
  that._userDefinedProgressCallback = userCallback;
  that._taskCompletedCallback = completedCallback;
  that._taskAbortedCallback = abortedCallback;
  return this;
};
// Функция отмены
that.abort = function () {
  if (_xhr !== null)
       xhr.abort();
};
...
// Вызываем по URL и отслеживаем прогресс
that.start = function (url, progressUrl) {
  that._taskId = that.createTaskId();
  that._progressUrl = progressUrl;
  // AJAX-вызов
    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();
            }
  });
  // Запускаем обратный вызов индикации прогресса (если есть)
  if (that._userDefinedProgressCallback == null)
      return this;
  that._timerId = window.setTimeout(
    that._internalProgressCallback,
    that._interval);
};
  return that;
}

Как видите, новый метод abort не делает ничего особенного помимо вызова abort во внутреннем объекте XmlHttpRequest. Однако отмена выполняемого AJAX-вызова все равно инициирует событие завершения в диспетчере AJAX. Чтобы распознать, была ли операция отменена пользователем, нужно подключить обработчик этого события (complete) и проверить свойство status объекта XmlHttpRequest: оно равно 0, если операция отменена. Обработчик complete затем выполняет любую необходимую очистку, например останавливает таймеры. На рис. 2 показан интерфейс, с помощью которого пользователи могут прекращать удаленные операции.

*
Рис. 2. Отменяемые AJAX-операции

Уведомление сервера

UI на рис. 2 не обязательно гарантирует, что серверная операция будет остановлена по запросу пользователя, хотя UI вроде бы подтверждает ее прекращение. Вызов abort в XmlHttpRequest просто закрывает сокет, который соединяет браузер с сервером. Иначе говоря, вызывая abort в XmlHttpRequest, вы просто сообщаете, что вас больше не интересует получение ответа от серверного метода. На самом деле ничто не гарантирует, что сервер принял и подтвердил запрос на отмену; скорее всего сервер продолжит обработку исходного запроса независимо от того, ожидает ли браузер ответ. Хотя именно этот аспект может варьироваться на разных платформах и серверах, существует другой аспект, который вы должны продумать и который прямо зависит от вашего приложения. Если запрос вызвал асинхронную или длительно выполняемую операцию, можно ли ее прекратить? По правде говоря, надежного и автоматизированного способа решения этой задачи нет; вы должны создать собственную инфраструктуру и написать свои серверные методы, поддерживающие отмену. Так что давайте расширим инфраструктуру индикации прогресса.

Расширение инфраструктуры индикации прогресса

Компонент диспетчера на серверной стороне является частью инфраструктуры, с которой работают контроллеры. Методы контроллера вызывают методы следующего интерфейса, чтобы асинхронно передавать сообщения для клиентского индикатора прогресса и принимать уведомления от 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);
}

По сравнению с кодом, который я представил в прошлой статье, теперь появились два дополнительных метода: RequestTermination (вызывается клиентами для запроса отмены) и ShouldTerminate (вызывается методами операций для проверки того, предполагается ли прекращение их работы и откат текущего состояния к исходному).

Каждый диспетчер индикации прогресса работает поверх провайдера данных, который хранит состояние выполняемых задач; при этом каждая задача имеет идентификатор, сгенерированный клиентом. Провайдер данных по умолчанию в исходном коде использует кеш ASP.NET для хранения состояния задач. Он создает элемент для каждой задачи и сохраняет его в объекте типа TaskStatus:

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)
  {
    ...
  }
}

Метод в примере возвращает String для простоты, но это может быть частичное представление, JSON или что-то еще, что нужно именно вам.

Базовый класс, показанный на рис. 3, — простой способ оснастить конечный контроллер набором общих методов. В частности, обратите внимание, что методы Status и Abort определяют открытый стандартный API, через который jQuery-клиенты запрашивают текущее состояние и отмену. Используя базовый класс, вы избегаете необходимости каждый раз переписывать этот код.

Рис. 3. Надкласс ProgressBar

public class ProgressBarController : Controller
{
  protected readonly ProgressManager ProgressManager;
  public ProgressBarController()
  {
    ProgressManager = new ProgressManager();
  }
  public String GetTaskId()
  {
    // Получаем заголовок с идентификатором задачи
    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();
  // Резервирование первого авиарейса в общем маршруте
  ProgressManager.SetCompleted(taskId,
    "Booking flight: {0}-{1} ...", from, to);
  Thread.Sleep(4000);
  if (ProgressManager.ShouldTerminate(taskId))
  {
    return String.Format(
      "One flight booked and then canceled");
  }
  // Резервирование обратного авиарейса
  ProgressManager.SetCompleted(taskId,
    "Booking flight: {0}-{1} ...", to, from));
  Thread.Sleep(4000);
  if (ProgressManager.ShouldTerminate(taskId))
  {
    return String.Format(
      "Two flights booked and then canceled");
  }
  // Возврат брони
  ProgressManager.SetCompleted(taskId,
    "Paying for the flight ...", taskId));
  Thread.Sleep(5000);
  if (ProgressManager.ShouldTerminate(taskId))
  {
    return String.Format(
      "Payment canceled. No flights booked.");
  }
  // Некое возвращаемое значение
  return "Flight booked successfully";
}

Идентификатор задачи генерируется на клиентской стороне и передается серверу через HTTP-заголовок. Метод GetTaskId избавляет разработчиков контроллеров от необходимости вникать в детали этого процесса. Метод контроллера выполняет свою работу порциями и вызывает SetCompleted всякий раз, когда заканчивает одну из порций этой работы. Он сообщает о проделанной работе, используя строку, которая может указывать процент выполнения задачи, а также может быть сообщением о состояние. Метод контроллера периодически проверяет, был ли получен запрос на завершение. Если таковой запрос был, он делает все возможное для отката или компенсации, а затем возвращает управление.

Сводим все воедино: клиент

На клиентской стороне вы должны связать Progress Framework Java¬Script 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 подключаются при загрузке страницы:
<script type="text/javascript">
  var progressbar;
  $(document).ready(function () {
    $("#buttonStart").bind("click", buttonStartHandler);
    $("#buttonAbort").bind("click", buttonAbortHandler);
  });
</script>

На рис. 6 показан JavaScript-код для запуска и отмены удаленной операции, а также для соответствующего обновления UI.

Рис. 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-объект ProgressBar включает три основных метода. Метод setInterval указывает интервал между двумя последовательными проверками на обновление состояния. Значение передается в миллисекундах. Метод callback определяет набор функций обратного вызова для обновления состояния и UI, когда операция успешно завершается или отменяется. Наконец, метод start запускает операцию. Он принимает три URL: конечной точки выполняемого метода и конечных точек для методов обратного вызова, инициируемых для захвата обновлений состояния и для отмены выполняемой операции.

Как видите, эти URL относительны и выражаются в форме /controller/method. Конечно, вы можете изменить имена status и abort на нужные вам, но только при условии, что такие методы существуют как общедоступные конечные точки. Методы status и abort гарантированно существуют, если вы наследуете свой класс контроллера от ProgressBarController. На рис. 7 показано приложение-пример в действии. Хотя UI на рис. 2 и 7 выглядят одинаково, нижележащий код и поведение заметно различаются.

*
Рис. 7. Инфраструктура в действии

Двигаемся дальше

AJAX предоставляет средства для опроса сервера, чтобы выяснить, что на нем происходит. Если вам нужно предоставлять отслеживаемые и прерываемые методы, вы должны создать собственную инфраструктуру. Следует отметить, что на практике сам метод action мог бы вызываться другими асинхронными сервисами. Такой код может оказаться весьма сложным. К счастью, написание асинхронного кода в предстоящей версии ASP.NET MVC 4 должно заметно упроститься. В следующей статье я покажу другой подход к реализации и мониторингу удаленных задач на основе новой клиентской библиотеки SignalR в ASP.NET MVC 4. А пока скачайте полный исходный код по ссылке code.msdn.microsoft.com/mag201201CuttingEdge!

Автор: Дино Эспозито  •  Иcточник: MSDN Magazine  •  Опубликована: 17.05.2012
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   ASP.NET, MVC.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.