(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

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

Дино Эспозито

Большинство пользователей какого-либо приложения хотят получать от него соответствующую обратную связь, когда оно переходит к выполнению потенциально длительной операции. Реализация этого шаблона в Windows-приложении - задача не сложная, хоть и не столь простая, как можно было бы ожидать. Но реализация того же шаблона в веб-приложении создает дополнительные трудности, которые в основном связаны с отсутствием поддержки состояний, заложенной в саму природу Web.

Веб-разработчикам в особенности проще вывести какое-либо статическое сообщение в начале операции. Так что в центре внимания этой статьи будет вовсе не то, как вывести строку текста вроде "Please wait" или анимированное изображение. Вместо этого мы озаботимся отчетами о состоянии удаленных операций, обеспечивая контекстно-зависимую обратную связь, которая точно отражает состояние операции для данного сеанса. В веб-приложениях длительно выполняемые операции осуществляются на сервере, но никаких встроенных механизмов, передающих информацию о состоянии в клиентский браузер, нет. Поэтому для такой цели придется создавать собственную инфраструктуру со смешанным клиентским и серверным кодом. Такие библиотеки, как jQuery, полезны, но готового решения не дают - даже с учетом широчайшего выбора плагинов jQuery.

В этой статье - первой из небольшой серии - я буду рассматривать самый популярный AJAX-шаблон, стоящий за любой инфраструктурой поддержки контекстно-зависимой индикации прогресса, и создам решение, ориентированное на приложения ASP.NET MVC. Если вы ведете разработку на основе Web Forms, то, возможно, захотите прочитать несколько статей, написанных мной летом 2007 г. и ориентированных на Web Forms и AJAX (список моих статей см. по ссылке bit.ly/psNZAc).


Шаблон Progress Indicator

Фундаментальная проблема с отчетом о прогрессе на основе AJAX заключается в следующем. Как обеспечить обратную связь о состоянии операции, пока пользователь ждет ответа сервера. Сформулируем иначе: пользователь запускает AJAX-операцию, требующую некоторого времени на выполнение, и в этот период он должен принимать обновления о достигнутом прогрессе. Архитектурная проблема состоит в том, что пользователь не получит частичные ответы; операция возвращает свой ответ, только когда будет выполнена вся работа на серверной стороне. Чтобы доставить частичные ответы клиенту, нужна некая разновидность синхронизации между AJAX-клиентом и серверным приложением. Эту задачу и решает шаблон Progress Indicator (индикатор прогресса).

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

Реализация этого шаблона требует писать серверные операции так, чтобы им были известны их роли. Например, вы реализуете серверную операцию на основе рабочего процесса. Перед тем как начинать каждый значимый этап рабочего процесса, операция будет запускать некий код, который обновляет надежное хранилище информацией, относящейся к выполняемой операции. Хранилищем может служить таблица базы данных или блок общей памяти. В качестве информации, связанной с выполняемой операцией, можно использовать некое значение, указывающее процент выполненной работы, или сообщение, уведомляющее об очередном этапе.

Попутно вам понадобится сервис на основе JavaScript, который параллельно считывает текст из надежного хранилища и передает его клиенту. Наконец, клиент будет использовать код на JavaScript для обработки и вывода текста в существующем UI. В итоге может показываться простой текст в теге DIV или нечто посложнее, скажем, индикатор прогресса на основе HTML.


Индикатор прогресса в ASP.NET MVC

Посмотрим на практике, как реализуется шаблон Progress Indicator в ASP.NET MVC. Серверная операция, по сути, является методом операции контроллера (controller action method). Контроллер может быть либо синхронным, либо асинхронным. Асинхронность в контроллерах положительно сказывается только на отзывчивости серверного приложения и никак не влияет на время ожидания пользователем результата запрошенной операции. Шаблон Progress Indicator отлично работает с контроллером любого типа..

Для вызова и последующего мониторинга серверной операции потребуется код на AJAX. Однако библиотеку AJAX не следует ограничивать выдачей запроса и ожиданием ответа. Библиотека также должна уметь настраивать таймер, который будет периодически инициировать вызов какой-либо конечной точки, возвращающей текущее состояние операции. По этой причине требуется jQuery или встроенный объект XMLHttpRequest, но и этого недостаточно. Учитывая это, я создал простой JavaScript-объект, который скрывает большую часть дополнительных действий, необходимых при AJAX-вызове с поддержкой мониторинга. Из представления ASP.NET MVC вы вызываете операцию именно через этот JavaScript-объект.

Метод контроллера, отвечающий за операцию, будет использовать серверную часть инфраструктуры для сохранения текущего состояния в известном (и общем) месте, например в кеше ASP.NET. Наконец, контроллер должен предоставлять общую конечную точку, которая будет регулярно вызываться по таймеру для чтения состояния в реальном времени. Для начала посмотрим на всю инфраструктуру в действии, а затем обсудим ее внутреннее устройство.


Знакомьтесь с инфраструктурой SimpleProgress Framework

Написанная специально для этой статьи, инфраструктура SimpleProgress Framework (SPF) состоит из файла JavaScript и библиотеки классов. В последней определяется один ключевой класс: ProgressManager; он управляет выполнением задачи и любыми операциями мониторинга. Пример метода операции контроллера, использующего эту инфраструктуру, показан на рис. 1. Заметьте, что этот (потенциально длительно выполняемый) код на самом деле следует размещать в асинхронном контроллере, чтобы избежать слишком длительной блокировки потока ASP.NET.

Рис. 1. Метод операции контроллера, использующий SimpleProgress Framework

public String BookFlight(String from, String to)
{
  var taskId = GetTaskId();
 
  // Бронирование билета на рейс "туда"
  ProgressManager.SetCompleted(taskId,
    String.Format("Booking flight: {0}-{1} ...", from, to));
  Thread.Sleep(2000);
 
  // Бронирование билета на рейс "обратно"
  ProgressManager.SetCompleted(taskId,
    String.Format("Booking flight: {0}-{1} ...", to, from));
  Thread.Sleep(1000);
 
  // Выкуп билетов
  ProgressManager.SetCompleted(taskId,
    String.Format("Paying for the flight ..."));
  Thread.Sleep(2000);
 
  // Какое-то возвращаемое значение
  return String.Format("Flight booked successfully");
}

Как видите, операция состоит из трех главных этапов. Для упрощения картины реальные действия опущены и заменены вызовом Thread.Sleep. Гораздо важнее три вызова SetCompleted, которые записывают текущее состояние работы, выполняемой методом, в общее место. Адрес этого места скрыт в классе ProgressManager. На рис. 2 показано, что нужно для вызова и мониторинга метода контроллера.

Рис. 2. Вызов и мониторинг метода через JavaScript

<script type="text/javascript">
  $(document).ready(function () {
    $("#buttonStart").bind("click", buttonStartHandler);
  });
 
  function buttonStartHandler() {
    new SimpleProgress()
    .setInterval(600)
    .callback(
      function (status) { $("#progressbar2").text(status); },
      function (response) { $("#progressbar2").text(
        response); })
    .start("/task/bookflight?from=Rome&to=NewYork",
      "/task/progress");
  }
</script>

Заметьте, что для большей читаемости кода на рис. 2 я отделил buttonStartHandler от обработчика ready. Но, сделав это, я внес некоторые излишества в глобально видимые функции JavaScript, так как мне пришлось определить новую глобально видимую функцию.

Сначала вы задаете несколько параметров, таких как URL, по которому нужно выполнять обратный вызов для получения текущего состояния, и обратные вызовы, которые нужно запускать для обновления индикатора прогресса, а также для обновления UI по окончании длительной операции. После этого запускаете выполнение операции.

Класс контроллера должен включить некоторые дополнительные средства: он должен предоставлять метод, подлежащий обратному вызову. Этот код является относительно стандартным, и я включил его в базовый класс, от которого вы можете наследовать свой контроллер, как показано ниже:

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

Весь класс SimpleProgressController приведен на рис. 3.

Рис. 3. Базовый класс для контроллеров с поддержкой мониторинга операций

public class SimpleProgressController : Controller
{
  protected readonly ProgressManager ProgressManager;
 
  public SimpleProgressController()
  {
    ProgressManager = new ProgressManager();
  }
 
  public String GetTaskId()
  {
    // Получаем заголовок с идентификатором задачи
    var id = Request.Headers[ProgressManager.HeaderNameTaskId];
    return id ?? String.Empty;
  }
 
  public String Progress()
  {
    var taskId = GetTaskId();
    return ProgressManager.GetStatus(taskId);
  }
}

В этом классе два метода. GetTaskId извлекает уникальный идентификатор задачи, который является ключом к получению состояния конкретного вызова. Как вы увидите потом, идентификатор задачи генерируется инфраструктурой JavaScript и передается с каждым запросом, используя собственный HTTP-заголовок. Второй метод в классе SimpleProgressController представляет открытую (и общую) конечную точку, которую инфраструктура JavaScript будет вызывать для получения состояния конкретного экземпляра задачи.

Прежде чем углубиться в некоторые детали реализации, взгляните на рис. 4, чтобы получить представление, каких результатов позволяет добиться SPF.

Рис. 4. Приложение-пример в действии


Класс ProgressManager

Класс ProgressManager предоставляет интерфейс для чтения и записи текущего состояния в общем хранилище. Класс основан на следующем интерфейсе:


public interface IProgressManager
{
  void SetCompleted(String taskId, Int32 percentage);
  void SetCompleted(String taskId, String step);
  String GetStatus(String taskId);
}

Метод SetCompleted сохраняет состояние как простую строку или значение в процентах. Метод GetStatus считывает любой контент. Общее хранилище абстрагируется интерфейсом IProgressDataProvider:


public interface IProgressDataProvider
{
  void Set(String taskId, String progress,
   Int32 durationInSeconds=300);
  String Get(String taskId);
}

Текущая реализация SPF поддерживает только один провайдер данных о прогрессе операции, который сохраняет свой контент в кеше ASP.NET. Ключ к идентификации состояния каждого запроса - идентификатор задачи. На рис. 5 показан пример провайдера данных о прогрессе операции (progress data provider).

Рис. 5. Провайдер данных о прогрессе операции на основе объекта кеша ASP.NET


public class AspnetProgressProvider : IProgressDataProvider
{
  public void Set(String taskId, String progress,
    Int32 durationInSeconds=3)
  {
    HttpContext.Current.Cache.Insert(
      taskId,
      progress,
      null,
      DateTime.Now.AddSeconds(durationInSeconds),
      Cache.NoSlidingExpiration);
  }
 
  public String Get(String taskId)
  {
    var o = HttpContext.Current.Cache[taskId];
    if (o == null)
      return String.Empty;
 
    return (String) o;
  }
}

Как упоминалось, каждый запрос на отслеживаемую задачу сопоставляется с уникальным идентификатором. Этот идентификатор представляет собой случайное число, генерируемое инфраструктурой JavaScript, и передается от клиента на сервер через HTTP-заголовок запроса.


Инфраструктура JavaScript

Одна из причин, по которой библиотека jQuery стала настолько популярной, - AJAX API. Это мощный с богатыми возможностями интерфейс; кроме того, он упрощает AJAX-вызовы за счет списка функций с сокращенными именами (shorthand functions). Однако исходный jQuery AJAX API не поддерживает мониторинг прогресса выполнения операций. Из-за этого нужен обертывающий API, который использует jQuery (или любую другую инфраструктуру JavaScript, которую вы предпочитаете) для размещения AJAX-вызова, в то же время гарантируя, что для каждого вызова генерируется случайный идентификатор задачи и активируется сервис мониторинга. На рис. 6 показан фрагмент файла SimpleProgressFx.js из пакета исходного кода, который можно скачать для этой статьи; он иллюстрирует логику, начинающую работать в момент удаленного вызова.

Рис. 6. Скриптовый код, запускающий SimpleProgress Framework


var SimpleProgress = function() {
  ...
  that.start = function (url, progressUrl) {
    that._taskId = that.createTaskId();
    that._progressUrl = progressUrl;
 
    // Размещаем AJAX-вызов
    $.ajax({
      url: url,
      cache: false,
      headers: { 'X-SimpleProgress-TaskId': that._taskId },
      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);
  };
}

После генерации идентификатора задачи эта функция добавляет его как собственный HTTP-заголовок. Сразу после запуска AJAX-вызова функция устанавливает таймер, периодически запускающий обратный вызов. Обратный вызов считывает текущее состояние и передает результат пользовательской функции для обновления UI.

Для выполнения AJAX-вызова я использую jQuery; в этом случае важно, чтобы вы отключили кеширование браузером AJAX-вызовов. В jQuery кеширование включено по умолчанию и автоматически отключается для определенных типов данных, таких как script и JSON With Padding (JSONP).

Отнюдь не простая задача

Мониторинг выполняемых операций - отнюдь не простая задача в веб-приложениях. Решения, основанные на опросе, весьма популярны, но не обязательны. Обратите внимание на интересный проект SignalR от GitHub, который реализует постоянные соединения в ASP.NET (github.com/signalr). С помощью SignalR вы можете решить ту же задачу, которая обсуждалась здесь, но обойтись без опроса на предмет изменений.

В этой статье я рассмотрел пример инфраструктуры (как клиентской, так и серверной), в которой сделана попытка упростить реализацию контекстно-зависимого индикатора прогресса. Хотя код оптимизирован для ASP.NET MVC, нижележащий шаблон абсолютно универсален и равно применим в приложениях ASP.NET Web Forms. Если вы скачаете исходный код и поэкспериментируете с ним, без колебаний делитесь со мной любыми соображениями и замечаниями.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 02.02.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год.
Microsoft 365 Business Basic (corporate)
Microsoft Office для дома и учебы 2019 (лицензия ESD)
Microsoft 365 Business Standard (corporate)
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год.
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
Компьютерные книги. Рецензии и отзывы
Новые материалы
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100