31 марта 2015
Кравченко Виктор

Как сделать индикатор прогресса (ProgressBar) обновляющийся с сервера (ASP.NET, AJAX, JQuery) — часть 1

VB.NET JQuery JavaScript HTML ASP.NET Web
01

Предположим, что в проекте существует некая функция/процедура, исполнение которой может быть затянуто на время, не сопоставимое со среднестатистическим временем загрузки страницы — 10 сек и более. К примеру, существует метод в контроллере, который собирает информацию с нескольких интернет страниц и выводит сводные результаты:

Статья написана на основе материалов статьи Дино Эспозито "Контекстно-зависимый индикатор прогресса для ASP.NET MVC" в Журнале MSDN Magazine за декабрь 2011.
02 VB.NET
1
2
3
4
5
6
7
8
9
Public Function SomeMethod() As ActionResult Dim results As String = "" For i As Integer = 0 To 10 results += ПолучитьСодержимоеСтраницы(url) Next Return View(results) End Function
03

О функции ПолучитьСодержимоеСтраницы() можно почитать в статье Как получить содержимое произвольной вебстраницы.

04
SimpleProgressBar в действии — информация о статусе получена с сервера.
05

Суть идеи прогресс-бара проста и заключается в следующем:

06
  • на сервере — в начале операции в кэше ASP.NET создается маркированная область, куда, исполняемая функция по мере выполнения операций, сохраняет значения статуса,
  • на клиенте — с заданной периодичностью страница запрашивает из известной области кэша данные о прогрессе и показывает их:
07
08

На примере встраивания в готовый проект покажу, как это работает. Серверная сторона состоит из 5 файлов:

09
  • 2 интерфейсов
10 VB.NET
1
2
3
4
Public Interface IProgressDataProvider Sub [Set](ByVal taskId As String, ByVal progress As String, Optional ByVal durationInSeconds As Int32 = 300) Function [Get](ByVal taskId As String) As String End Interface
IProgressDataProvider.vb
11 VB.NET
1
2
3
4
5
Public Interface IProgressManager Sub SetCompleted(ByVal taskId As String, ByVal percentage As Int32) Sub SetCompleted(ByVal taskId As String, ByVal [step] As String) Function GetStatus(ByVal taskId As String) As String End Interface
IProgressManager.vb
12
  • провайдера AspnetProgressProvider, реализующего интерфейс IProgressDataProvider и взаимодействующего с кэшем:
13 VB.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Public Class AspnetProgressProvider Implements IProgressDataProvider
Public Sub [Set](ByVal taskId As String, ByVal progress As String, Optional ByVal durationInSeconds As Int32 = 300) Implements Abstractions.IProgressDataProvider.Set HttpContext.Current.Cache.Insert(taskId, progress, Nothing, Date.Now.AddSeconds(durationInSeconds), Cache.NoSlidingExpiration) End Sub
Public Function [Get](ByVal taskId As String) As String Implements Abstractions.IProgressDataProvider.Get Dim o = HttpContext.Current.Cache(taskId) If o Is Nothing Then Return String.Empty End If Return CType(o, String) End Function
End Class
AspnetProgressProvider.vb
14
  • объекта-класса ProgressManager, реализующего весь функционал серверной стороны:
15 VB.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Imports Codius.Framework.Abstractions
Namespace Framework
Public Class ProgressManager Implements IProgressManager Public Const HeaderNameTaskId As String = "X-SimpleProgress-TaskId" Private ReadOnly _dataProvider As IProgressDataProvider
Public Sub New() Me.New(New AspnetProgressProvider()) End Sub
Public Sub New(ByVal dataProvider As IProgressDataProvider) _dataProvider = dataProvider End Sub
Public Sub SetCompleted(ByVal taskId As String, ByVal percentage As Int32) Implements Abstractions.IProgressManager.SetCompleted If String.IsNullOrEmpty(taskId) Then Exit Sub End If percentage = If(percentage < 0, 0, percentage) percentage = If(percentage > 100, 100, percentage) Dim [step] = String.Format("{0} %", percentage) _dataProvider.Set(taskId, [step]) End Sub
Public Sub SetCompleted(ByVal taskId As String, ByVal [step] As String) Implements Abstractions.IProgressManager.SetCompleted If String.IsNullOrEmpty(taskId) Then Exit Sub End If _dataProvider.Set(taskId, [step]) End Sub
Public Function GetStatus(ByVal taskId As String) As String Implements Abstractions.IProgressManager.GetStatus Const invalidStatus As String = "..." If String.IsNullOrEmpty(taskId) Then Return invalidStatus End If Dim buffer = _dataProvider.Get(taskId) Return If(String.IsNullOrEmpty(buffer), invalidStatus, buffer) End Function
End Class
End Namespace
ProgressManager.vb
16
  • и контроллера SimpleProgressController, позволяющий взаимодействовать клиенту с сервером (объект ProgressManager):
17 VB.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Namespace Framework
Public Class SimpleProgressController Inherits Controller Protected ReadOnly ProgressManager As ProgressManager
Public Sub New() ProgressManager = New ProgressManager() End Sub
Public Function GetTaskId() As String ' Get the header with the task ID Dim id = Request.Headers(ProgressManager.HeaderNameTaskId) Return If(id, String.Empty) End Function
Public Function Progress() As String Dim taskId = GetTaskId() Return ProgressManager.GetStatus(taskId) End Function
End Class
End Namespace
SimpleProgressController.vb
18

Клиентская сторона состоит из единственного скрипта SimpleProgress-Fx.js — объект-функция SimpleProgress, которая берет на себя весь клиентский функционал:

19 JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
var SimpleProgress = function () { var that = {}; // Get the current task ID that._taskId = 0; // Get the timer ID used to push refreshes that._timerId = 0; // Get the URL to invoke to read status that._progressUrl = ""; // Get the current interval for progress refresh that._interval = 500; // Get the user-defined callback that refreshes the UI that._userDefinedProgressCallback = null; // Get the user-defined callback that finalizes the call that._taskCompletedCallback = null; // Get a new task ID that.createTaskId = function () { var _minNumber = 100, _maxNumber = 1000000000; return _minNumber + Math.floor(Math.random() * _maxNumber); }; // Set progress callback that.callback = function (userCallback, completedCallback) { that._userDefinedProgressCallback = userCallback; that._taskCompletedCallback = completedCallback; return this; }; // Set frequency of refresh that.setInterval = function (interval) { that._interval = interval; return this; }; // INTERNAL FUNCTION that._internalProgressCallback = function () { that._timerId = window.setTimeout(that._internalProgressCallback, that._interval); $.ajax({ url: that._progressUrl, cache: false, headers: { 'X-SimpleProgress-TaskId': that._taskId }, success: function (status) { if (that._userDefinedProgressCallback != null) that._userDefinedProgressCallback(status); } }); }; // Invoke the URL and monitor its progress that.start = function (url, progressUrl) { that._taskId = that.createTaskId(); that._progressUrl = progressUrl; // Place the Ajax call $.ajax({ url: url, cache: false, headers: { 'X-SimpleProgress-TaskId': that._taskId }, success: function (data) { if (that._taskCompletedCallback != null) that._taskCompletedCallback(data); that.end(); } }); // Start the callback (if any) if (that._userDefinedProgressCallback == null) return this; that._timerId = window.setTimeout(that._internalProgressCallback, that._interval); }; // Finalize the task that.end = function () { that._taskId = 0; window.clearTimeout(that._timerId); } return that; };
SimpleProgress-Fx.js
20

При встраивании в готовый проект, после копирования всех необходимых файлов, и создания ссылок на них (например на скрипт SimpleProgress-Fx.js), необходимо переунаследовать контроллер, в котором будет происходить длительная операция, от класса SimpleProgressController. Поскольку он также унаследован от обычного Controller существующий код модифицировать не придется (кроме конечно же переунаследования).

21 На заметку:
Единственный нюанс, на который необходимо обратить внимание, заключается в том, чтобы в существующем контроллере отсутствовал метод Progress, поскольку он будет конфликтовать с встроенным методом Progress родительского контроллера SimpleProgressController. Также, обязательно нужно проверить, чтобы существующие правила роутинга (процедура RegisterRoutes) давали доступ к этому методу, иначе клиент при обращении к этому методу не будет получать ответ.
22

Пример. У нас есть контроллер HomeController:

23 VB.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Imports Codius.Framework Imports System.Threading
Public Class HomeController Inherits SimpleProgressController ' Здесь было Inherits Controller - заменяем на наш модифицированный контроллер.
Function Index() As ActionResult Return View() End Function
Public Function DoWork(ByVal repeat As Int32) As String ' Это первая длительная операция Dim taskId = GetTaskId() For i = 1 To repeat ' Объект ProgressManager является частью унаследованного SimpleProgressController ProgressManager.SetCompleted(taskId, String.Format("Iteration #{0} completed...", i)) ' Даем команду внести в кэш изенения статуса Thread.Sleep(1000) Next i ' Some return value Return String.Format("Task completed @{0}", Date.Now.ToString("h:mm:ss")) End Function
Public Function BookFlight(ByVal [from] As String, ByVal [to] As String) As String ' Это вторая длительная операция Dim taskId = GetTaskId() ' Book first leg ProgressManager.SetCompleted(taskId, String.Format("{2} || Booking flight: {0}-{1} ...", [from], [to], taskId)) Thread.Sleep(2000) ' Book return ProgressManager.SetCompleted(taskId, String.Format("{2} || Booking flight: {0}-{1} ...", [to], [from], taskId)) Thread.Sleep(1000) ' Book return ProgressManager.SetCompleted(taskId, String.Format("{0} || Paying for the flight ...", taskId)) Thread.Sleep(2000) ' Some return value Return String.Format("{0} || Flight booked successfully", taskId) End Function
Public Function DoWorkProgress() As String ' Это третья длительная операция Dim taskId = GetTaskId() For i As Integer = 0 To 100 ProgressManager.SetCompleted(taskId, i) Thread.Sleep(Rnd(1) * 300) Next Return String.Format("{0} Завершено!", taskId) End Function
End Class
HomeController.vb
24

и представление View — метода Index:

25 HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
@Code Layout = Nothing End Code <!DOCTYPE html>
<html>
<head> <meta name="viewport" content="width=device-width" /> <title>Codius.ru - SimpleProgressBar Sample</title> <script src="~/Script/SimpleProgress-Fx.js"></script> @*Добавляем ссылку на SimpleProgress-Fx.js*@ <script src="~/Script/jquery-1.10.2.min.js"></script> @*Не забываем про JQuery*@ </head>
<body>
<style type="text/css"> #prBarLine { position: relative; display: inline-block; padding:1px; border: 1px solid rgb(200,200,200); height: 6px; margin-top:.4em; width:300px; } #prBarLineBlock { position: relative; height: 100%; background-color: rgba(0, 85, 204, 0.8); } </style>
<script type="text/javascript"> $(document).ready(function () { // После загрузки всей страницы привязываем обработчики нажатия кнопок $("#buttonStart1").bind("click", button1StartHandler); $("#buttonStart2").bind("click", button2StartHandler); $("#buttonStart3").bind("click", button3StartHandler); }); function button1StartHandler() { var p1 = new SimpleProgress(); // можно так объявлять p1.setInterval(300) // Устанавливаем интервал обращений к серверу за состоянием в миллисекундах .callback(function (status) { $("#prBarText1").text(status); }, function (response) { $("#prBarText1").text(response); }) .start("/home/dowork?repeat=5", "/home/progress"); } function button2StartHandler() { new SimpleProgress() // а можно и так .setInterval(600) .callback(function (status) { $("#prBarText2").text(status); }, function (response) { $("#prBarText2").text(response); }) .start("/home/bookflight?from=Rome&to=NewYork", "/home/progress"); } function button3StartHandler() { $("#prBarText2").text(""); new SimpleProgress() .setInterval(300) .callback(function (status) { // Функция получения информации о статусе // поскольку здесь у нас статус в процентах, поэтому мы его обрабатываем по другому $('#prBarLineBlock').animate({ 'width': w }, 100); $("#prBarText3").text(status); var w = status.replace(' ', ''); $('#prBarLineBlock').animate({ 'width': w }, 100); // без анимации: //$("#prBarLineBlock").width(w); // или так: // $("#prBarLineBlock").css('width', w); }, function (response) { // Получен ответ от сервера - значит длительная процедура завершена $("#prBarText3").text("Завершено!"); $('#prBarLineBlock').animate({ 'width': '100%' }, 100); }) .start("/home/doworkprogress", "/home/progress"); } </script>
<div style="text-align:center; padding-top:2em;"> Примеры от Dino Esposito<br /> <input id="buttonStart1" type="button" value="Start task" /> <div id="prBarText1"> @*Сюда будем выводить информацию о статусе 1*@ </div> <br /> <input id="buttonStart2" type="button" value="Book a flight..." /> <div id="prBarText2"> @*Сюда будем выводить информацию о статусе 2*@ </div> <br /><br /> Пример от Codius.ru<br /> <input id="buttonStart3" type="button" value="Старт" /> <div id="prBarText3"></div> <div id="prBarLine"> <div id="prBarLineBlock" style="width:0%"> @*А это прогресс бар 3*@ </div> </div> </div> </body>
</html>
Index.vbhtml
26 На заметку:
Файл проекта можно скачать по ссылке Codius.SimpleProgressBar_v1.0.rar (47,0 KB). Проект создан в Visual Studio 2013 Ultimate.
27

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

28

О том как будет расширен функционал нашего ProgressBar — в статье Как сделать индикатор прогресса (ProgressBar) обновляющийся с сервера (ASP.NET, AJAX, JQuery) - часть 2

30

Похожие запросы:

  • Client Server Progress Bar
  • Reporting Progress from Tasks
  • jQuery Progress Bar in ASP.Net
  • Progress bar for long running server calls in ASP.Net MVC
  • Reporting Progress from Async Tasks
comments powered by HyperComments