ASP.NET AJAX: Отключение пользовательского интерфейса во время выполнения частичного постбэка
ОГЛАВЛЕНИЕ
Если частичный постбэк был вызван из того же UpdatePanel в то же самое время как другой частичный постбэк уже был вызван и выполняется, то постбэк, который вызвали первым, прекращается, и начинается выполнение второго постбэка. Прекращение частичного постбэка означает, что ASP.NET AJAX Framework в браузере уже не ожидает ответа с сервера на отосланный запрос. Оно не останавливает обработку на сервере и не производит откат изменений состояния, которые могли произойти на сервере. Следовательно, если при частичном постбэке добавляются записи в базу данных или выполняются какие-либо другие изменения, для которых пользователь нажимает какую-то кнопку в UpdatePanel, тем самым вызывая частичный постбэк, а затем повторно нажмет ту же самую кнопку во время выполнения первого постбэка, то в базу данных будут добавлены две одинаковые записи.
Существует несколько путей предотвращения такого двойного вызова частичного постбэка. Самым эффективным способом, по-моему, является "блокировка" области путем прикрытия экрана элементом <div>. (Рассмотрите последний пример в статье "Предоставляем визуальный фидбэк с помощью элемента управления UpdateProgress".) Другим вариантом является отключение элемента пользовательского интерфейса, вызвавшего частичный постбэк во время выполнения другого постбэка. Это предотвратит повторный вызов частичного постбэка. Читайте далее, чтобы больше узнать об этом!
Необходимость в отключении пользовательского интерфейса во время выполнения частичного постбэка
Приведенное в конце данной статьи Приложение, которое вы можете загрузить, включает в себя демо-страницу, названную NoDisabling.aspx и демонстрирующую обычное поведение. Демо-страница состоит из элемента управления UpdatePanel, содержащего два веб-элемента управления Button (Кнопка). При первой загрузке страницы сессионная переменная (Session), названная NumberOfClicks, установлена в 0:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
Session("NumberOfClicks") = 0
End If
End Sub
В обработчике события Click кнопки (Button) данная сессионная переменная инкрементируется и кнопка, которая была нажата, а также значение данной сессионной переменной отображаются в элементе Label, названном Results. Более того, оба обработчика события Click "спят" 5 секунд, что добавляет искусственную задержку при выполнении частичного постбэка. Данная задержка необходима для имитации реальной ситуации, когда необходимо некоторое количество времени, чтобы выполнить частичный постбэк. Обработчик события Click для элемента Button1 должен выглядеть следующим образом:
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
Session("NumberOfClicks") += 1
Results.Text = String.Format("Button 1 was clicked... The Buttons have been clicked {0:d} times", Session("NumberOfClicks"))
Thread.Sleep(5000)
End Sub
В статье "Осуществляем действия клиента в ответ на частичные постбэки" демонстрируется путь выполнения клиентского скрипта на различных этапах жизненного цикла частичного постбэка. Если вы еще не ознакомились с вышеназванной статьей, то я советую вам прочитать её перед дальнейшим чтением данной статьи. В статье я продемонстрировал путь создания журнала, записывающего события с клиентской стороны и отображающего сообщение всякий раз, как только клиентское событие вызовет объект PageRequestManager. (который отображает сообщение каждый раз как клиентское события вызовет объект PageRequestManager.) Приложение с кодом включает в себя данную функциональность.
Чтобы увидеть данную необходимость в отключении пользовательского интерфейса во время частичного постбэка, откройте в вашем браузере страницу с примером. Нажмите на первую кнопку, чтобы вызвать частичный постбэк. Журнал событий отобразит следующие записи:
- initializeRequest
- beginRequest
Следующее событие – endRequest - не отобразится, если частичный постбэк был прекращен или же пока не будет получен ответ с сервера. Во время частичного постбэка повторно нажмите первую кнопку. Это начнет второй частичный постбэк. На первой стадии второго частичного постбэка (initializeRequest) прекращается первый частичный постбэк. Данное поведение можно заметить, судя по записям в журнале событий, на следующем изображении. Заметьте, что первые два события - initializeRequest и beginRequest - были вызваны в 11:08:14 первичным нажатием на кнопку "Button 1". Двумя секундами позже я повторно нажал кнопку "Button 1". Это создало третью запись в журнале событий (initializeRequest), в 11:08:16. Поскольку первый постбэк был все еще в силе, он был автоматически прекращен. Четвертая запись журнала событий (endRequest) указывает на завершение первого частичного постбэка. Пятая запись (beginRequest) оповещает о второй стадии выполнения второго частичного постбэка.
Важно понять, что прекращение частичного постбэка останавливает ожидание ответа, но оно не останавливает обработку на сервере, а также не производит откат уже совершенных изменений. Это наглядно видно по результату, отображенному в элементе Results Label при завершении второго частичного постбэка. Несмотря на то, что первый частичный постбэк был прекращен, сессионная переменная (Session) была дважды инкрементирована.
В случаях, когда частичный постбэк производит какие-либо изменения, все предусмотрено, и пользователь не сможет повторно вызвать частичный постбэк во время выполнения уже вызванного. Данная статья рассматривает метод реализации такой функциональности путем отключения элемента пользовательского интерфейса, вызвавшего частичный постбэк.
Обнаружение и отключение того элемента пользовательского интерфейса, который вызвал выполнение частичного постбэка
Как мы уже видели, в то время как начинается выполнение частичного постбэка, PageRequestManager вызывает серию событий клиентской стороны. Первым является initializeRequest. Затем вызывается событие beginRequest. beginRequest передается в args (аргументах) объекта и включает в себя информацию о том элементе пользовательского интерфейса, который вызвал частичный постбэк. В частности, данная информация может быть получена при помощи args.get_postBackElement(). Как только мы получим ссылку на тот элемент пользовательского интерфейса, который вызвал постбэк, мы сможем отключить его, путем установки его свойства disabled в значение True ("Истина"):
function PageRequestManager_beginRequest(sender, args)
{
var postbackElem = args.get_postBackElement();
postbackElem.disabled = true;
}
Данный код отключает элемент пользовательского интерфейса при старте выполнения частичного постбэка (до того, как HTTP-запрос передан с клиента на сервер). Тем не менее, данный код в отдельности непригоден - нам необходимо активировать элемент пользовательского интерфейса в момент, когда закончится выполнение частичного постбэка.
Активируем элемент пользовательского интерфейса, вызвавшего выполнение частичного постбэка
PageRequestManager вызывает свое событие endRequestпри завершении выполнения частичного постбэка независимо от того, был ли частичный постбэк успешно завершен, завершился ошибкой, или же был отменен либо прекращен пользовательским действием. Поэтому следующим шагом должно быть восстановление работоспособности элемента пользовательского интерфейса в обработчике события endRequest.
К сожалению, обработчику события endRequest не передается ссылка на элемент управления, вызвавшего выполение частичного постбэка. Вследствие этого, нам нужно сохранить эту информацию в переменной страничного уровня. Следующий скрипт JavaScript демонстрирует реализацию данной функциональности. Данной переменной - uiId - присваивается значение атрибута id того элемента управления, который вызвал выполнение частичного постбэка в обработчике события beginRequest. Далее она используется в обработчике события endRequest для получения ссылки на элемент и устанавливает его свойство disabled в значении false ("Ложь").
var uiId = '';
function PageRequestManager_beginRequest(sender, args)
{
var postbackElem = args.get_postBackElement();
uiId = postbackElem.id;
postbackElem.disabled = true;
}
function PageRequestManager_endRequest(sender, args)
{
$get(uiId).disabled = false;
}
Заметьте, что в обработчике события endRequest я использую функцию $get(id) для получения ссылки на элемент, указанный в ID значении переменнойuiId. Функция $get(id) является частью клиентского API в ASP.NET AJAX Framework. Оно служит кратчайшим путем к функции getElementById класса Sys.UI.DomElement.
Тестируем данную функциональность пользовательского интерфейса
Приведенные в конце данной статьи файлы включают в себя файл, названныйDisabling.aspx, который имеет тот же пользовательский интерфейс, что и NoDisabling.aspx, но реализует логику активации\деактивации, которую мы только что обсудили, на клиентской стороне. Следующее изображение демонстрирует страницу сразу после того, как была нажата кнопка "Button 1". Заметьте, что кнопка "Button 1" отключена - она не будет реагировать на щелчки до тех пор, пока не будет восстановлена.

После завершения частичного постбэка кнопка "Button 1" восстановлена и может быть опять нажата для вызова другого частичного постбэка.

Остановка других частичных постбэков
Деактивация элемента пользовательского интерфейса, который вызывает частичный постбэк, обеспечивает уверенность в том, что этот самый элемент управления не вызовет другой постбэк, тем самым прекращая текущий запрос. Но что если какой-либо другой элемент в том же UpdatePanel вызовет частичный постбэк? Например, что случится, если в файле Disabling.aspx пользователь нажмет кнопку "Button 1" и затем, во время выполнения постбэка, нажмет кнопку "Button 2"? Нажатие на кнопку "Button 2" прекратит выполнение постбэка, вызванного нажатием кнопки "Button 1", и затем начнет выполнение собственного постбэка.
Чтобы предотвратить это мы можем отключить весь пользовательский интерфейс на время выполнения частичного постбэка (вместо того, чтобы отключать только кнопку "Button 1"). Изучите последний пример из статьи "Предоставляем визуальный фидбэк с помощью элемента управления UpdateProgress". Другим выходом из данной ситуации будет программное предотвращение запуска частичного постбэка в случае, если другой уже выполняется.
Объект sender, переданный обработчику события initializeRequest, обладает функцией, названной get_isInAsyncPostBack(), которая возвращает логическое значение (Boolean), указывающее на то, выполняется ли в данный момент частичный постбэк. Чтобы разрешить выполнение единственного постбэка, изучите данную функцию и отмените текущий запрос в случае, если она возвращает значение True ("Истина"). Следующий скрипт JavaScript демонстрирует способ достижения данного результата:
function PageRequestManager_initializeRequest(sender, args)
{
if (sender.get_isInAsyncPostBack())
{
args.set_cancel(true);
alert('The page is currently serving a request. Please wait until this request completes, then try again.');
}
}
В дополнение к отмене запроса на выполенение частичного постбэка, код, указанный выше, также отображает оповещение , объясняющее пользователю, что его новый запрос не может быть обработан, пока текущий запрос не завершится. Следующее изображение демонстрирует данную функциональность в действии. Кнопка "Button 1" была нажата во время выполнения частичного постбэка. Кнопка деактивирована благодаря добавлению предыдущего кода. Если пользователь нажимает кнопку "Button 2" во время частичного постбэка, то соответствующее окно с оповещением будет отображено, и запрос на выполнение частичного постбэка, вызванного нажатием на кнопку "Button 2", будет отменен.

Вывод
В некоторых условиях частичный постбэк выполняет некоторые действия на сервере, которые влияют на веб-приложение. В данном случае очень важно убедиться в том, что пользователь не сможет повторно вызвать выполнение частичного постбэка. Например, если частичный постбэк осуществляет вставку записи в базу данных, то вызов одного и того же частичного постбэка несколько раз (к примеру, нажимая на кнопку несколько раз) приведет к дупликации записей. Чтобы не допустить этого, вы можете либо полностью блокировать весь пользовательский интерфейс путем накрытия страницы элементом <div> , либо вы можете отключить элемент пользовательского интерфейса, вызвавшего постбэк. Последний вариант был рассмотрен в данной статье.
Веселого программирования!
Scott Mitchel