Использование AJAX в ASP.NET
История развития web-приложений Олег Шаститко
🕛 13.03.2008, 15:02
Рассматривая ту или иную новую технологию прежде всего необходимо разобраться для чего эта самая технология необходима и что именно она позволяет (или облегчает) делать, что не позволяют делать другие технологии. Чтобы более наглядно понять, какие предпосылки были к появлению AJAX, можно рассмотреть процесс эволюционного развития web-сайтов.На самой заре развития интернет сайты представляли собой набор простых статических страниц - пользователь запрашивал ресурс у сервера и сервер возвращал статическую страницу. Эта страница представляла собой простой HTML текст и хранилась как текстовый файл на сервере. Данная страница поставлялась пользователю as is и, кроме того, не имела никаких клиентских скриптов.
CGI
Первая попытка сделать страницы более динамичными был Common Gateway Interface (CGI). CGI позволял разработчику создавать исполняемые программы, которые генерировали страницу, что позволяло принимать параметры от пользователя. С учетом этих параметров можно было генерировать уникальную страницу. По большому счету, данный подход используется и сейчас в том же ASP.NET, PHP и т.д.
JavaScript
Генерация страниц на серверной стороне это конечно хорошо, но это вынуждало при каждом, даже самом незначительном действии пользователя, делать запрос к серверу с перегрузкой всей страницы. Чтобы не перегружать всю страницу в случаях, когда участие сервера абсолютно не нужно, стали использоваться клиентские скрипты.
Апплеты и Flash
В случае, если запрос к серверу все же необходим и одними клиентскими скриптами не обойтись - разработчики в web-страницах стали использовать т.н. апплеты а также flash.
Апплеты впервые были использованы в 1995 году, когда Sun представила миру на всеобщее обозрение свою новую платформу с новым языком - JAVA. По сути апплеты представляли собой программы, написанные на JAVA и которые могли запускаться в броузере как отдельные приложения. Для того, чтобы эта программа, написанная на неведомом для броузера языке JAVA, могла быть запущена внутри броузера, необходимо было установить JVM (Java Virtual Machine) - среду для выполнения программ JAVA. И хотя данный подход звучит очень заманчиво - он имел множество недостатков, прежде всего это проблемы безопасности (не каждый пользователь, путешествуя по интернет, разрешит запускать на своем компьютере программы непонятного происхождения) и необходимость громозкой VM. Несмотря на то, что помимо общения с сервером апплеты и флеш также позволяли реализовать возможности, недоступные JavaScript (скажем, более высокие требования к графике) - те сложности не дали апплетам прижиться настолько, чтобы окончательно решить проблемы с запросами к серверу.
DHTML
Dynamic HTML объединил в себе HTML, каскадные таблицы стилей (CSS) и JavaScript. Также ко всему этому набору добавился DOM - объектная модель броузера. Вся эта смесь позволяла (и позволяет) успешно создавать очень красивые, удобные и функциональные страницы «на лету». Но опять же, в случае, если нужно выполнить запрос к серверу - приходится перегружать весь документ.
AJAX
Решение этой проблемы пришло с появлением новой технологии, которая в 2004 году была названа AJAX (Asynchronous JavaScript + XML). Данная технология построена на принципе выполнения запроса к серверу с использованием JavaScript и получению результата опять же, с помощью JavaScript, что позволяет избежать перегрузки страницы и следовательно имеет несколько неоспоримых преимуществ:
1. На сервер отправляются не все элементы страницы (точнее не их значения), а только те минимальные данные, которые необходимы для выполнения того или иного запроса и в ответ принимается не вся страница, а только необходимые данные, что позволяет уменьшить трафик в десятки (а иногда и в сотни) раз. 2. Не происходит перегрузка страницы в броузере и у пользователя создается впечатление, что все происходит на его компьютере.
Об этой технологии и пойдет дальше речь.
Объектная модель броузера.
Если вы меня спросите на чем основан принцип работы технологии AJAX, то я вам наверное отвечу : «благодаря объектной модели броузера». Что же это за такая объектная модель броузера (DOM)?
Document Object Model (DOM) - это спецификация, стандартизированная W3C комитетом, которая является кроссплатформенной и описывает вызовы и описания, касающиеся действиям с самим документом, его структурой, HTML, XML и стилями. Как следует из названия, основой спецификации DOM являются объекты.
Объект XMLHttpRequest
Этот объект появился впервые в Internet Explorer 5.0 и был реализован как ActiveX компонент. Важно заметить, что этот объект не является стандартом W3C, хотя многое из его функциональности описано в спецификации «The DOM Level 3 Load and Save Specification». По этой причине его поведение может немного отличаться в различных броузерах. Но во всех броузерах он выполняет одну и ту же функциональность - он умеет посылать запросы к серверу и получать от него ответы. Как уже говорилось выше, данный объект не стандартизирован и создание его instance может отличаться в различных версиях, поэтому для «надежного» его создания лучше использовать код, который объединяет в себе создание instance в нескольких броузерах подобно коду ниже:
var xmlHttp;
function createXMLHttpRequest()
{
if (window.ActiveXObject)
{
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
else if (window.XMLHttpRequest)
{
xmlHttp = new XMLHttpRequest();
}
}
XMLHttpRequest имеет ряд «стандартных» (стандартных в кавычках т.к. как писалось выше, данный объект не стандартизирован для всех броузеров) свойств и методов, которые позволяют данному объекту посылать запрос, проверять состояние запроса и получать результат выполнения запроса от удаленного сервера. Эти свойства и методы мы рассмотрим в следующих двух таблицах.
В таблице 1 представлены «стандартные» свойства XMLHttpRequest
Метод
Описание
abort()
Прерывает текущий запрос
getAllResponseHeaders()
Возвращает все заголовки Response в виде ключ/значение
getResponseHeader(header)
Возвращает значение определенного заголовка
open(method, url, asynch, username, password)
Устанавливает состояние запроса к серверу. Первый параметр указывает метод запроса - PUT, GET, POST, второй - url запроса, третий (необязательный) - тип запроса (синхронный или асинхронный), четвертый и пятый (также необязательные) - для защищенных страниц
send(content)
Посылает запрос серверу
setRequestHeader(header, value)
Устанавливает значение определенного заголовка. Перед вызовом этого метода необходимо вызвать метод open
Также XMLHttpRequest содержит ряд свойств, которые представлены ниже:
Свойство
Описание
onreadystatechange
Обработчик события, которое возникает при каждой смене состояния запроса
readyState
Состояние запроса. Доступны следующие значения: 0 - запрос неинициализирован, 1 - загрузка, 2 - загрузка окончена, 3 - interactive, 4 - complete
responseText
Ответ от сервера в виде строки
responseXML
Ответ от сервера в XML. Этот объект может быть обработан и проверен как DOM
status
Код статуса HTML.(например 200 - OK)
statusText
Название кода статуса HTML
Бегло проглянув эти методы, несложно понять, какие методы достаточно вызвать, чтобы с помощью JavaScript получить какие-либо с сервера.
Прежде всего напишем серверную часть, которая будет возвращать простую строку. Для этого (чтобы не было «посторонних» данных вроде тегов открытия закрытия html) наиболее рационально будет создать hanlder. Открываем web-проект в Visual Studio 2005 и создаем файл типа Handler. Содержимое будет примерно следующим:
<%@ WebHandler Language="C#" Class="MyHandler" %>
using System;
using System.Web;
public class MyHandler: IHttpHandler {
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/plain";
context.Response.Write("Hello World");
}
public bool IsReusable {
get {
return false;
}
}
}
Т.е. при запросе данной страницы этот hanlder возвращает text/plain документ с единственной строчкой “Hello World”. Нас такой handler устраивает как нельзя лучше.
Теперь создадим обычную HTML - страницу, которая будет и выполнять запрос, используя XMLHttpRequest.
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Simple XMLHttpRequest page</title>
<script type="text/javascript">
var xmlHttp;
function createXMLHttpRequest()
{
if (window.ActiveXObject)
{
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
else if (window.XMLHttpRequest)
{
xmlHttp = new XMLHttpRequest();
}
}
function startRequest()
{
createXMLHttpRequest();
xmlHttp.onreadystatechange = handleStateChange;
xmlHttp.open("GET", "MyHandler.ashx", true);
xmlHttp.send(null);
}
function handleStateChange()
{
if(xmlHttp.readyState == 4)
{
if(xmlHttp.status == 200)
{
alert("Response: " + xmlHttp.responseText);
}
}
}
</script>
</head>
<body>
<input type="button" value="Start Asynchronous Request"
onclick="startRequest();"/>
</body>
</html>
Данный код довольно прост. При нажатии на кнопку “Start Asynchronous Request” вызывается клиентская функция startRequest, которая в свою очередь сначала вызывает рассмотренную нами ранее функцию createXMLHttpRequest для создания объекта XMLHttpRequest, после чего цепляет обработчик (клиентскую функцию handleStateChange) на событие ReadyStateChange для этого объекта, открывает и посылает запрос. Если запрашиваемая страница доступна и данные были получены, status меняет свое состояние на 200. Поэтому в функции handleStateChange мы проверяем значение этого свойства. При нужном значении мы с помощью alert выводим полученное значение.
В данном несложном коде по сути заложена вся фукциональность AJAX - получение данных от сервера без перегрузки страницы. Понимания этого механизма достаточно, чтобы понять суть AJAX, а также успешно использовать его в своих приложениях. Далее только дело техники и далее мы рассмотрим реализацию всего этого, но с использованием ASP.NET J
Использование AJAX в ASP.NET
Обратные вызовы страницы.
Обратные вызовы - это специальный вид возврата формы, т.е. страница проходит свой цикл событий, но данные формы возвращаются клиенту до начала процесса рендеринга формы, т.е. до перерисовки. Как и в любом AJAX исполнении запрос начинается на клиентской стороне в результате возникновения какого-либо события, при этом запускается клиентская встроенная функция под названием WebForm_DoCallback. Эта функция имеет следующий прототип:
WebForm_DoCallback(pageID, argument, returnCallback, context, errorCallback, useAsync);
Где:
pageID - ID страницы, которая выполняет вызов,
argument - строковый аргумент, передаваемый серверу,
returnCallback - клиентская функция или клиентский скрипт, который должен выполниться после того, как серверная сторона вернет управление
context - данные, которые передаются returnCallback.
errorCallback - клиентская функция или клиентский скрипт, выполняемый при возникновении ошибок
useAsync - устанавливает, будет ли запрос синхронным или асинхронным.
Следующий этап - серверная страница должна знать, что она должна поддерживать обратные вызовы (т.е. прежде всего возвращать данные до начала рендеринга страницы). Для этого эта страница должна реализовывать интерфейс System.Web.UI.IcallbackEventHandler.
Данный интерфейс содержит 2 метода:
public interface ICallbackEventHandler
{
string GetCallbackResult();
void RaiseCallbackEvent(string eventArgument);
}
Выполнение обратного вызова на серверной стороне состоит из 2-х этапов: подготовка и возвращение результата. Метод RaiseCallbackEvent вызывается первым и предназначен для подготовки удаленного выполнения кода. Метод GetCallbackResult выполняется позже, когда результат уже готов к отправке. Данное разделение было введено только в release версии .NET 2.0, в предыдущих версиях эти 2 методы были объединены в один (это было сделано с учетом асинхронной работы). Метод GetCallbackResult возвращает string, поэтому возвращаемые данные должны быть сериализированы тем или иным методом в строку, а на клиенте наоборот, десериализированы.
При запросе страницы с клиентского скрипта сначала выполняется Init, после чего стандартный цикл событий загрузки страницы до события Load, в Load свойство IsCallback устанавливается в true, по завершению Load выполняются методы интерфейса ICallbackEventHandler, после чего как уже говорилось выше выполнение прерывается, не переходя в стадию рендеринга. Прежде всего это говорит о том, что не происходит стадия сохранения ViewState, так что пытаться что-либо сохранить в ViewState стандартным способом бесполезно (оно и понятно, т.к. ViewState страницы не обновляется). Управляет процессом взаимодействия между страницей и сервером т.н. диспетчер обратных вызовов. Диспетчер обратных вызовов имеет в себе библиотеку клиентских сценариев. Эти клиентские сценарии формируют и отправляют запрос, получают и разбирают ответ от сервера и т.д. Посмотрев View Source любой из страниц можно увидеть строки вроде
<script src="http://dev.net.ua/WebResource.axd?d=VWLFgbEM584QkLzy5eDwGw2&t=632964616335443742" type="text/javascript"></script>
Скачав файл, который возвращает данный обработчик WebResource.axd с указанными параметрами, можно углубиться в изучения клиентских скриптов, которые отвечают за указанные выше действия J
Метод GetCallbackEventReference
Написание клиентского метода WebForm_DoCallback не является чем-либо сложным, однако связано с некоторыми трудностями в случае динамического генерирования или передачей параметров. Для этого в класс Page.ClientScript (System.Web.UI.ClientScriptManager) введен специальный метод - GetCallbackEventReference, который получает ряд параметров и, подобно например методу GetPostBackEventReference, генерирует соответствующий клиентский код. Я бы не сказал, что этот метод очень изящный, особенно когда необходимо передать параметры в клиенский скрипт (особенно наличие одинарных и двойных кавычек в контатенирующейся строке портит всю картину), но все поудобнее, чем в лоб писать WebForm_DoCallback.
Данный метод имеет следующий прототип:
public string GetCallbackEventReference(
Control target,
string argument,
string clientCallback,
string context,
string clientErrorCallback,
bool useAsync)
где:
target - страница или WebControl, который будет обрабатывать обратный вызов. Соответственно, эта страница или контрол должны реализовать интерфейс ICallbackEventHandler, иначе будет брошено исключение:
System.InvalidOperationException: The target '_Page' for the callback could not be found or did not implement ICallbackEventHandler.
Генерует первый параметр функции WebForm_DoCallback
argument - аргумент, передаваемый клиентской функции или скрипту. Соответствует второму параметру функции WebForm_DoCallback.
returnCallback - клиентская функция или клиентский скрипт, который должен выполниться после того, как серверная сторона вернет управление (3-й параметр WebForm_DoCallback)
context - данные, которые передаются клиентской returnCallback (4-й параметр WebForm_DoCallback).
errorCallback - клиентская функция или клиентский скрипт, выполняемый при возникновении ошибок (5-й параметр WebForm_DoCallback)
useAsync - устанавливает, будет ли запрос синхронным или асинхронным (6-й параметр WebForm_DoCallback).
Теперь мы можем создать нашу первую aspx страницу с использованием AJAX. Допустим, довольно частая задача - есть 2 выпадающих списка - в одном из них данные более высокого уровня, нежели во втором. Т.е. при выборе значения в первом списке - второй список перегружается в зависимости от выбранного значения в первом. Например - первый SELECT содержит список производителей машин, второй - модели машин, которые выбранный производитель выпускает. Для решения этой задачи можно конечно загрузить все данные в javascript, а потом скриптом перебиндивать второй select. В нашем случае это в принципе подошло бы, но есть несколько минусов - первый это то, что данных может быть очень много (допустим, 100 производителей и для каждого из них указаны все модели, когда либо выпускавшиеся, т.е. может быть более 100. Итого грузить несколько десятков тысяч итемов в javascript не есть лучший вариант). Кроме того, прийдется писать не такой уж и простой яваскрипт. Плюс может быть ситуация, когда данные должны быть интерактивными.
Попробуем решить это с помощью AJAX.
Для этого создаем обычную ASPX страницу:
<form id="form1" runat="server">
<div>
<table>
<tr>
<td>
Please, select Manufacturer:
</td>
<td>
<asp:DropDownList runat="server" ID="ddlManufacturer"></asp:DropDownList>
</td>
</tr>
<tr>
<td>
Please, select Model:
</td>
<td>
<asp:DropDownList runat="server" ID="ddlModel"></asp:DropDownList>
</td>
</tr>
</table>
</div>
</form>
После чего реализуем интерфейс ICallbackEventHandler для класса страницы:
public partial class CarList : System.Web.UI.Page, ICallbackEventHandler
{
protected string evArg;
protected void Page_Load(object sender, EventArgs e)
{
}
#region ICallbackEventHandler Members
public string GetCallbackResult()
{
throw new Exception("The method or operation is not implemented.");
}
public void RaiseCallbackEvent(string eventArgument)
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
Нам необходимо получить eventArgument в методе RaiseCallbackEvent, сделать соответствующие действия и потом передать его в GetCallbackResult для возврата клиенту. Для этого мы вводим переменную evArg.
Далее нам необходимо прицепить клиентский обработчик для первого SELECT. Т.е. при смене значения должна вызываться функция WebForm_DoCallback. Так и пишем J
protected void Page_Load(object sender, EventArgs e)
{
string argClientFunction = "document.all['" + ddlManufacturer.ClientID + "'].options(document.all['" + ddlManufacturer.ClientID + "'].selectedIndex).value";
string cScript = ClientScript.GetCallbackEventReference(this, argClientFunction, "CallbackFunction", "'CallbackContext'", "null", false);
ddlManufacturer.Attributes.Add("onchange", cScript + ";return false;");
}
Теперь напишем 2 метода, первый из которых нужен только один раз - для binding списка производителей, а второй - для списка моделей. Чтобы не мучаться с БД сделаем попроще следующим образом:
void BindManufacturers()
{
ddlManufacturers.Items.Add(new ListItem("Mercedes"));
ddlManufacturers.Items.Add(new ListItem("BMW"));
ddlManufacturers.Items.Add(new ListItem("Renault"));
ddlManufacturers.Items.Add(new ListItem("Toyota"));
ddlManufacturers.Items.Add(new ListItem("Daewoo"));
}
void BindModels(string manufacturer)
{
switch (manufacturer)
{
case "Mercedes":
ddlModel.Items.Clear();
ddlModel.Items.Add(new ListItem("S350"));
ddlModel.Items.Add(new ListItem("S500"));
ddlModel.Items.Add(new ListItem("S600"));
ddlModel.Items.Add(new ListItem("CLK"));
break;
case "BMW":
ddlModel.Items.Clear();
ddlModel.Items.Add(new ListItem("model 3"));
ddlModel.Items.Add(new ListItem("model 5"));
ddlModel.Items.Add(new ListItem("model 7"));
ddlModel.Items.Add(new ListItem("X3"));
ddlModel.Items.Add(new ListItem("X5"));
break;
case "Renault":
ddlModel.Items.Clear();
ddlModel.Items.Add(new ListItem("12"));
ddlModel.Items.Add(new ListItem("19"));
ddlModel.Items.Add(new ListItem("21"));
break;
case "Toyota":
ddlModel.Items.Clear();
ddlModel.Items.Add(new ListItem("Aristo"));
ddlModel.Items.Add(new ListItem("Avalon"));
ddlModel.Items.Add(new ListItem("Avensis"));
ddlModel.Items.Add(new ListItem("Bandeirante"));
break;
case "Daewoo":
ddlModel.Items.Clear();
ddlModel.Items.Add(new ListItem("Sens"));
ddlModel.Items.Add(new ListItem("Lanos"));
break;
}
}
И следовательно для того, чтобы заполнить список производителей, необходимо добавить в Page_Load:
if (!IsPostBack)
BindManufacturers();
Теперь осталось сделать 2 вещи - написать клиентскую функцию, которая будет выполняться после возврата и написать код для функций RaiseCallbackEvent и GetCallbackResult.
Сначала напишем код для этих 2-х функций:
public string GetCallbackResult()
{
BindModels(evArg);
evArg = String.Empty;
for (int i = 0; i < ddlModel.Items.Count; i++)
{
evArg += ddlModel.Items[ i ].Value + ";";
}
return evArg;
}
public void RaiseCallbackEvent(string eventArgument)
{
evArg = eventArgument;
}
Значение параметра eventArgument функции RaiseCallbackEvent заносим во внутреннюю переменную evArg (это значение выбранного производителя), а в GetCallbackResult перебиндив список моделей в эту же переменную заносим все значения моделей для данного производителя, разделив их например «;» (этакий способ сериализации).
Далее напишем клиентскую функцию CallbackFunction. Данная функция должна очищать SELECT для моделей и, получив строковое значение от GetCallbackResult, десериализировать это значение и заполнить SELECT новыми значениями.
string scr = "<script language=javascript>";
scr += "function CallbackFunction(callbackResult, callbackContext)";
scr += "{";
scr += " var ddlModel = document.all['" + ddlModel.ClientID + "'];";
scr += " var l = ddlModel.options.length;";
scr += " for(var i=0; i<l; i++)";
scr += " ddlModel.options.remove(0);";
scr += " var models = callbackResult.split(';');";
scr += " for(var i=0; i<models.length; i++)";
scr += " {";
scr += " var oOption = document.createElement(\"OPTION\");";
scr += " oOption.text = models[ i ];";
scr += " oOption.value = models[ i ];";
scr += " ddlModel.options.add(oOption);";
scr += " }";
scr += " return false;";
scr += "}";
scr += "</script>";
RegisterStartupScript("scr" + this.ClientID, scr);
Вот в принципе и всё. Данный пример конечно далеко не совершенен (например, не учтена обработка ошибок), но вполне работоспособен.
Библотека ATLAS
Чтобы облегчить жизнь простому программисту при работе с AJAX и избавить его от необходимости вникать в подробности взаимодействия клиента с сервером - Microsoft разработала библиотеку, содержащую ряд компонентов, использующих AJAX. Для большинства случаев при использовании этих компонентов даже нет необходимости знать принципы работы AJAX. Компоненты ATLAS размещаются на aspx-странице подобно пользовательским компонентам (по сути ими же и являясь), так что программирование с их использованием ничем принципиально не отличается от программирования с использованием пользовательских компонентов. Microsoft постоянно обновляет этот список, поэтому я бы советовал почаще посещать web-сайт http://atlas.asp.net для обновления текущей используемой версии на более новую (кроме того, промежуточные версии новых ATLAS-контролов или более ранних контролов, но с исправленными ошибками можно взять с сайта: ... ).
Для того, чтобы добавить в свой проект ATLAS компоненты нужно во-первых убедиться, что ATLAS установлен на вашем компьютере, в противном случае его установить. После чего можно либо создать проект типа “ATLAS” Web Site, либо добавить ссылку на библиотеку. Во втором случае необходимо настроить также web.config на использование ATLAS вручную.
На момент написания статьи ATLAS включал в себя 21 контрол, некоторые из которых используются довольно часто, другие же лично я кроме как в тестовых приложениях нигде не использовал. Контрол CascadingDropDown является ATLAS реализацией того, что мы написали выше J Наиболее широкоиспользуемый и универсальный, на мой взгляд, это контрол UpdatePanel - это панель (на клиенте она преобразуется в div элемент), которую можно перегрузить не перегружая страницу, причем она может содержать другие контролы. UpdatePanel состоит из 2-х частей - ContentTemplate и Triggers. ContentTemplate представляет собой область содержимого ContentTemplate (т.е. как я уже говорил, оно может быть из других ASP.NET контролов, тегов HTML и просто текста). Triggers содержит список триггеров для данной UpdatePanel. Триггер - это как-бы обработчик события, по которому данная UpdatePanel должна обновляться. Триггеры бывают 2-х типов - ControlEventTrigger и ControlValueTrigger. Первый из них реагирует на события контрола (например, контролом является Button и перехватываемым событием событие Click), второй - на изменение свойства (например изменение значения Text для контрола TextBox). Например, следующий код:
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
<atlas:ScriptManager EnablePartialRendering="true" ID="scriptManager" runat="server" ></atlas:ScriptManager>
</head>
<body>
<form id="form1" runat="server">
<div>
<atlas:UpdatePanel runat="server" ID="myUpdatePanel" Mode="Conditional">
<ContentTemplate>
<asp:Label runat="server" ID="lbl"></asp:Label>
</ContentTemplate>
<Triggers>
<atlas:ControlEventTrigger ControlID="btn" EventName="Click" />
</Triggers>
</atlas:UpdatePanel>
<br />
<asp:Button runat="server" ID="btn" Text="Click Me" OnClick="btn_Click" />
</div>
</form>
</body>
</html>
public partial class test_ajax1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e) { }
protected void btn_Click(object sender, EventArgs e)
{
lbl.Text = "Button was clicked";
}
}
Будет обновлять значение Label по нажатию кнопки с использованием AJAX. Обратите внимание, что данный код практически ничем не отличается от «классического» ASP.NET кода. Переместив Label из UpdatePanel - мы получим «классический» вариант : была нажата кнопка - произведен запрос к серверу - страница прошла полный цикл загрузки страницы - обновлено значение элемента Label - страница возвращена клиенту. Это позволяет легко адаптировать под использование ATLAS и наоборот. Однако есть еще одна строчка в коде, на которую вы наверное не обратили внимание - это ScriptManager в заголовке страницы. Данный элемент добавляет в возвращаемую клиенту страницу все необходимые для ATLAS клиентские скрипты и этот элемент является обязательным (и в единственном экземпляре на странице) в случае использования ATLAS. Также хочу предупредить, что в случае использования ControlValueTrigger необходимо на проверяемый контрол AutoPostBack="true", иначе он просто не будет срабатывать. Для того, чтобы укрепить свои знания и попробовать использование ATLAS на практике -создадим простейший чат. Но в нашем чате будет одна особенность - он будет использовать AJAX технологию и благодаря этому он будет интерактивным. В чатах прежних времен приходилось либо время от времени (например каждые 20 сек) перегружать всю страницу - при этом сразу всплывало несколько крупных недостатков:
1. (и самый большой) если пользователь набирал текст и в этот момент подходило время перегрузки - пользователь терял фокус ввода и соответственно ту часть сообщения, которую он уже набрал
2. перегрузка заставляла пользователя ждать, пока страница будет загружена и соответственно ему приходилось лицезреть на белую страницу с индикатором загрузки внизу
3. перегрузку нельзя было сделать слишком частой, т.к. помимо самого текста приходилось скачивать также все атрибуты страницы - картинки, логотипы, баннеры, разметку HTML
Для борьбы с этим явлением стали использовать фреймы, что позволяло перегружать только один фрейм, при этом пользователь не терял фокус и не приходилось загружать часть страницы, что находится вне данного фрейма. Однако недостатки как перегружаемый фрейм с белым фоном и резметка HTML все равно остались, плюс к ним добавились недостатки использования самих фреймов. Использование AJAX позволяет полностью избавиться от всех этих недостатков - данные передаются только те, которые нужно, а именно только текст (без разметки HTML - разметка устанавливается javascript'ом) и в оптимальном варианте только НОВЫЕ сообщения (при этом старые по мере надобности удаляются также javascript'ом). Таким образом снижение трафика в сотни, а то и тысячи, раз позволяет уменьшить время между запросами вплоть до нескольких секунд. При этом пользователь не видит процесса загрузки - ему кажется, что все происходит на его машине.
Создание такого чата с учетом всех возможностей AJAX я предоставлю читателю, а вот с использованием компонента ATLAS - UpdatePanel - мы создадим сейчас.
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
<atlas:ScriptManager EnablePartialRendering="true" ID="scriptManager" runat="server" />
</head>
<body>
<form id="form1" runat="server">
<div>
<atlas:UpdatePanel runat="server" ID="up" Mode="Conditional">
<ContentTemplate>
<asp:Literal runat="server" ID="lc"></asp:Literal>
</ContentTemplate>
<Triggers>
<atlas:ControlEventTrigger ControlID="btn" EventName="Click" />
</Triggers>
</atlas:UpdatePanel>
<br />
<table>
<tr>
<td>Your name:</td>
<td>
<asp:TextBox runat="server" ID="txtName"></asp:TextBox>
</td>
</tr>
<tr>
<td>Your Message:</td>
<td>
<asp:TextBox runat="server" ID="txtMessage" TextMode="MultiLine"></asp:TextBox>
</td>
</tr>
<tr>
<td colspan="2">
<asp:Button runat="server" ID="btn" Text="Add message" OnClick="btn_Click" />
</td>
</tr>
</table>
</div>
</form>
</body>
</html>
public partial class chat : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e) { }
protected void btn_Click(object sender, EventArgs e)
{
string mes = txtName.Text + " says> " + txtMessage.Text + "<BR>";
// используем самое доступное хранилище для сообщений - Application.
if (Application["messages"]==null)
{
Application["messages"] = mes;
}
else
{
Application["messages"] = ((string)Application["messages"]) + mes;
}
lc.Text = Application["messages"].ToString();
}
}
В данном примере триггер для UpdatePanel висит на кнопку btn, т.е. UpdatePanel будет обновляться только в случае нажатия на эту кнопку. Нам же необходимо чтобы UpdatePanel обновлялся также через определенные промежутки времени. Нет стандартных способов заставить обновляться UpdatePanel с помощью javascript. Я делаю это следующим образом:
1. На форму добавляю еще одну кнопку и делаю её невидимой.
<asp:Button runat="server" ID="btnUpdate" Visible="false" />
2. Создаю следующий javascript :
string scr = "<script language=javascript>";
scr += "function UpdatingPanel()";
scr += "{";
scr += " " + Page.ClientScript.GetPostBackEventReference(btnUpdate, "") + ";";
scr += "}";
scr += "</script>";
RegisterStartupScript("scr" + this.ClientID, scr);
3. В UpdatePanel добавляю триггер для этой кнопки:
<atlas:ControlEventTrigger ControlID="btnUpdate" EventName="Click" />
Также нужно прописать в <%@ Page %> - EnableEventValidation="false"
4. А также в тег body: onload="setInterval('UpdatingPanel()', 3000);"
Как это работает - клиентская функция UpdatingPanel грубо говоря имитирует нажатие на кнопку btnUpdate. Т.к. в UpdatePanel есть триггер для этой кнопки - то вызов этой клиенской функции вызовет обновление UpdatePanel. Нам остается только вызывать эту клиентскую функцию через определенные промежутки времени, что мы и делаем в body.
В результате у нас получился следующий код :
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="chat.aspx.cs" Inherits="test_chat" EnableEventValidation="false" %>
<%@ Register Assembly="AtlasControlToolkit" Namespace="AtlasControlToolkit" TagPrefix="atlasToolkit" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
<atlas:ScriptManager EnablePartialRendering="true" ID="scriptManager" runat="server" />
</head>
<body onload="setInterval('UpdatingPanel()', 3000);">
<form id="form1" runat="server">
<div>
<asp:Button runat="server" ID="btnUpdate" Visible="false" />
<atlas:UpdatePanel runat="server" ID="up" Mode="Conditional">
<ContentTemplate>
<asp:Literal runat="server" ID="lc"></asp:Literal>
</ContentTemplate>
<Triggers>
<atlas:ControlEventTrigger ControlID="btn" EventName="Click" />
<atlas:ControlEventTrigger ControlID="btnUpdate" EventName="Click" />
</Triggers>
</atlas:UpdatePanel>
<br />
<table>
<tr>
<td>Your name:</td>
<td>
<asp:TextBox runat="server" ID="txtName"></asp:TextBox>
</td>
</tr>
<tr>
<td>Your Message:</td>
<td>
<asp:TextBox runat="server" ID="txtMessage" TextMode="MultiLine"></asp:TextBox>
</td>
</tr>
<tr>
<td colspan="2">
<asp:Button runat="server" ID="btn" Text="Add message" OnClick="btn_Click" />
</td>
</tr>
</table>
</div>
</form>
</body>
</html>
public partial class chat : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string scr = "<script language=javascript>";
scr += "function UpdatingPanel()";
scr += "{";
scr += " " + Page.ClientScript.GetPostBackEventReference(btnUpdate, "") + ";";
scr += "}";
scr += "</script>";
RegisterStartupScript("scr" + this.ClientID, scr);
InitMessage();
}
void InitMessage()
{
if (Application["messages"] != null)
lc.Text = Application["messages"].ToString();
}
protected void btn_Click(object sender, EventArgs e)
{
string mes = txtName.Text + " says> " + txtMessage.Text + "<BR>";
if (Application["messages"]==null)
Application["messages"] = mes;
else
Application["messages"] = ((string)Application["messages"]) + mes;
lc.Text = Application["messages"].ToString();
}
}
Для того, чтобы попробовать этот простейший вариант чата с использованием ATLAS на практике - откройте несколько броузеров с данной страницей и в каждом из них напишите по сообщению. При этом можете видеть, что после написания во втором броузере сообщения в первом оно появится спустя 3 секунды.
В данной статье я описал основы работы с AJAX в ASP.NET и надеюсь, что она поможет программистам начать использование этой технологии в своих ASP.NET приложениях.
P.S. Когда писалась эта статья в последний день Microsoft выпустил вместо ATLAS новую версию AJAX beta 1. Как «мигрировать» с приложения, использующего ATLAS к использованию AJAX beta 1 вы можете почитать на странице ajax.asp.net/ajaxtoolkit/Walkthrough/AtlasToAspNetAjax.aspx