Обзор Ajax

162

Одна из главных причин использования кода JavaScript разработчиками - желание избежать обратной отправки. Например, рассмотрим элемент управления TreeView, который позволяет пользователям развертывать и свертывать узлы по своему желанию.

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

Мы уже видели, как можно избежать этих издержек и создать более эффективные и оптимизированные страницы с помощью небольшого объема кода JavaScript. Тем не менее, большинство рассмотренных до сих пор примеров JavaScript были самодостаточными - иными словами, они реализовали отдельную задачу, которая не требовала взаимодействия с остальной частью модели страницы.

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

Уже давно в сфере разработки веб-приложений появился - Ajax (Asynchronous JavaScript and XML). Он представляет собой приложение JavaScript, отличающееся одной специальной характеристикой, а именно - страницы в стиле Ajax обмениваются данными с сервером в фоновом режиме для запроса дополнительной информации. Когда клиентский код получает эту информацию (которая может быть передана в виде XML-пакета), он предпринимает дополнительные действия.

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

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

ASP.NET движется в этом направлении, но это продвижение происходит медленно - в конце концов, Microsoft требуется время, чтобы тщательно рассмотреть различные способы, которыми эти клиентские функции могут быть интегрированы в серверную модель ASP.NET.

В одной из следующих статей будет рассмотрено средство ASP.NET AJAX, которое предлагает высокоуровневый способ создания страницы в стиле Ajax, а сейчас давайте пробежимся по основам Ajax.

Объект XMLHttpRequest

Краеугольным камнем Ajax является объект XMLHttpRequest. Этот объект чрезвычайно полезен и обманчиво прост. По сути, он позволяет асинхронно отправлять запросы серверу и получать результаты в виде текста. А решение о том, что запрашивать, как обрабатывать запрос на стороне сервера и что возвращать клиенту, возлагается на разработчика.

Хотя современные браузеры предоставляют широкую поддержку объекта XMLHttpRequest, существуют тонкие различия в способе получения к нему доступа. В некоторых браузерах, включая Internet Explorer 7, Firefox, Safari и Opera, объект XMLHttpRequest реализован в виде собственного объекта JavaScript. В версиях, предшествующих Internet Explorer 7, он реализован как объект ActiveX. Вследствие этих различий код JavaScript должен быть достаточно интеллектуальным, чтобы использовать правильный подход при создании экземпляра XMLHttpRequest. Ниже приведен клиентский код JavaScript, который Microsoft применяет при решении этой задачи для клиентской функции обратного вызова:

var xmlRequest;
try
{
    // Этот код работает, если XMLHttpRequest является частью JavaScript
    xmlRequest = new XMLHttpRequest();
}
catch(err)
{
    // В противном случае необходим объект ActiveX
    xmlRequest = new ActiveXObject("Microsoft.XMLHTTP");
}

// Так или иначе, к этому моменту xmlRequest должен
// ссылаться на действующий экземпляр

Отправка запроса

Для отправки запроса с помощью объекта XMLHttpRequest будут использоваться два метода: open() и send().

Метод open() устанавливает вызов - он определяет запрос, который требуется отправить серверу. У него есть два обязательных параметра - тип команды HTTP (GET, POST или PUT) и URL-адрес. Например:

xmlRequest.open("POST", "Default.aspx");

Дополнительно можно предоставить третий параметр, который указывает, должен ли запрос выполняться асинхронно, и еще два параметра, чтобы предоставить информацию об имени пользователя и пароле для аутентификации. Однако применение параметров имени пользователя и пароля маловероятно, поскольку эта информация не может быть безопасно жестко закодирована в коде JavaScript. Клиентский код никогда не может служить подходящим местом для реализации функций обеспечения безопасности.

По умолчанию все запросы, выполняемые с помощью объекта XMLHttpRequest, являются асинхронными, И в основном нет причин изменять это поведение. Если решите выполнять вызов синхронно, с тем же успехом можно инициировать обратную отправку - в конце концов, пользователь будет неспособен сделать что-либо, пока страница заморожена и ожидает ответа. Отсутствие асинхронности означает отсутствие Ajax.

Метод send() отправляет запрос. Если запрос является асинхронным, он выполняет возврат немедленно:

xmlRequest.send(null);

Метод send() принимает единственный необязательный строковый параметр. Его можно применять для предоставления дополнительной информации, отправляемой с запросом, такой как значения, которые отправляются с запросом POST.

Обработка ответа

Очевидно, что был упущен один нюанс. Мы выяснили, как отправить запрос, но как обработать ответ? Секрет в том, чтобы присоединить обработчик события, используя свойство onreadystatechange. Это свойство указывает на клиентскую функцию JavaScript, которая вызывается, когда запрос завершен и данные доступны:

xmlRequest.onreadystatechange = UpdatePage;

Конечно, обработчик события должен быть присоединен до вызова метода send() для запуска запроса.

Когда ответ возвращен с сервера и функция инициирована, необходимую информацию можно извлечь из объекта xmlRequest через свойства responseText и responseXML. Свойство responseText предоставляет все содержимое как одну длинную строку. Свойство responseXML возвращает содержимое в виде дерева узловых объектов.

Несмотря на то что название Ajax подразумевает XML-содержимое, из сервера можно также вернуть что-нибудь другое, в том числе простой текст. Например, если сервер возвращает единственный фрагмент данных, нет никакого смысла помещать его в полный XML-документ.

Пример использования Ajax

Теперь, когда мы бегло ознакомились с объектом XMLHttpRequest, его можно использовать в простой странице. Чтобы создать страницу в стиле Ajax внутри приложения ASP.NET, нужны два компонента:

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

В следующем примере серверная функциональность реализована в виде обработчика HTTP. Обработчик HTTP принимает информацию через строку запроса (в данном случае он проверяет два параметра), а затем возвращает два фрагмента информации. Первый фрагмент - сумма двух аргументов, которые были переданы посредством строки запроса. Второй фрагмент - текущее время на веб-сервере. Информация не является стандартным XML-документом - вместо этого два значения просто разделяются запятой.

Ниже приведен полный код обработчика HTTP (чтобы добавить его в проект выберите в меню Website --> Add New Item --> Generic Handler):

using System;
using System.Web;

public class CalculatorCallbackHandler : IHttpHandler
{   
    public void ProcessRequest (HttpContext context)
    {
        HttpResponse response = context.Response;

        // Вывести обычный текст
        response.ContentType = "text/plain";

        // Получить аргументы строки запроса        
        float value1, value2;
        if (Single.TryParse(context.Request.QueryString["value1"], out value1) &&
            Single.TryParse(context.Request.QueryString["value2"], out value2))
        {
            // Вычислить сумму
            response.Write(value1 + value2);
            response.Write(",");

            // Вернуть текущее время
            DateTime now = DateTime.Now;
            response.Write(now.ToLongTimeString());
        }
        else
        {
            // Значения не были предоставлены, или они не были числами.
            // Указать на ошибку
            response.Write("-");
        }
    }
 
    public bool IsReusable
    {
        get
        {
            return true;
        }
    }
}

В этом примере задача обработчика HTTP невероятно проста. В конце концов, если бы просто интересовало сложение двух чисел, клиентский код мог бы решить эту задачу без Ajax-запроса. Такой подход становится более важным, когда серверный код должен сделать что-то, не доступное клиенту, например, найти информацию в серверном ресурсе (файле или базе данных), использовать конфиденциальную информацию (такую как секретные числа) или выполнить сложные операции с помощью классов, которые доступны только в .NET Framework.

Теперь, когда обработчик HTTP готов, его можно вызвать в любое время с помощью объекта XMLHttpRequest. На рисунке ниже показан пример страницы, которая выдает запрос каждый раз, когда пользователь нажимает клавишу в любом текстовом поле. Запрос предоставляет значения из двух текстовых полей, а результат отображается в затененном поле в нижней части страницы. Для подтверждения, что Ajax работает, в верхней части страницы выводится анимированное GIF-изображение. Обратите внимание, что во время обратного вызова формы в восковом светильнике продолжают непрерывно видоизменяться:

Страница в стиле Ajax

В основном, без учета кода JavaScript, работа страницы происходит следующим образом. Обратите внимание, что страница подключается к клиентскому коду JavaScript двумя способами. Во-первых, событие onload в дескрипторе <body> запускает функцию CreateXMLHttpRequest(), которая создает объект XMLHttpRequest. Во-вторых, два текстовых поля используют событие onKeyUp, чтобы инициировать вызов функции CallServerForUpdate():

<html>
<head runat="server">
    <title>Основы ASP.NET</title>
    <script type="text/javascript">
        // Здесь находится JavaScript-код
        
    </script>
</head>
<body onload="CreateXMLHttpRequest();">
    <form id="form1" runat="server">
        <div>
            <table style="width: 296px">
                <tr>
                    <td style="width: 55px">
                        <img src="lava_lamp.gif" alt="Animated Lamp" /></td>
                    <td style="font-size: x-small; width: 190px; font-family: Verdana">
                        Обратите внимание, что анимация GIF-изображения не останавливается, при синхронном 
                        запросе страница полностью бы обновилась.
                    </td>
                </tr>
            </table>
            <br />
            <br />
            Число 1: 
            <asp:TextBox ID="txt1" runat="server" onKeyUp="CallServerForUpdate();"></asp:TextBox><br />
            Число 2: 
            <asp:TextBox ID="txt2" runat="server" onKeyUp="CallServerForUpdate();"></asp:TextBox>
            <br /><br />
            <asp:Label ID="lblResponse" runat="server" BackColor="#FFFFC0" BorderStyle="Groove"
                BorderWidth="2px" Style="padding-right: 10px; padding-left: 10px; padding-bottom: 10px; padding-top: 10px"
                Width="440px" Font-Bold="True" Font-Names="Verdana" Font-Size="Small">
            </asp:Label>
        </div>
    </form>
</body>
</html>

Для создания соответствующей версии объекта XMLHttpRequest функция CreateXMLHttpRequest() использует рассмотренный ранее подход. Функция CallServerForUpdate() находит объекты текстового поля, захватывает их текущие значения и применяет их для построения URL-адреса, который указывает на обработчик HTTP. Затем код отправляет асинхронный запрос GET обработчику HTTP. И, наконец, когда ответ получен, запускается функция ApplyUpdate(). При условии отсутствия каких-либо ошибок новая информация извлекается из возвращенного текста и используется для создания сообщения, которое отображается в метке:

var xmlRequest;

function CreateXMLHttpRequest() {
    try {
        // Этот код работает, если XMLHttpRequest является частью JavaScript
        xmlRequest = new XMLHttpRequest();
    }
    catch (err) {
        // В противном случае требуется объект ActiveX
        xmlRequest = new ActiveXObject("Microsoft.XMLHTTP");
    }
}

function CallServerForUpdate() {
    var txt1 = document.getElementById("txt1");
    var txt2 = document.getElementById("txt2");

    var url = "CalculatorCallbackHandler.ashx?value1=" +
      txt1.value + "&value2=" + txt2.value;
    xmlRequest.open("GET", url);
    xmlRequest.onreadystatechange = ApplyUpdate;
    xmlRequest.send(null);
}

function ApplyUpdate() {
    // Проверить успешность получения ответа
    if (xmlRequest.readyState == 4) {
        if (xmlRequest.status == 200) {
            var lbl = document.getElementById("lblResponse");

            var response = xmlRequest.responseText;

            if (response == "-") {
                lbl.innerHTML = "Вы ввели некорректные числа.";
            }
            else {
                var responseStrings = response.split(",");
                lbl.innerHTML = "Сумма чисел: " +
                  responseStrings[0] + " <br>Сейчас: " + responseStrings[1];
            }
        }
    }
}

Этот код проверяет значение readyState, чтобы удостовериться в получении ответа. Значение readyState начинается с 0 в момент создания объекта XMLHttpRequest, затем изменяется на 1 при вызове метода open(), затем сменяется на 2 после отправки запроса, потом изменяется на 3 при получении ответа и, наконец, становится равным 4, когда ответ полностью загружен. Если значение readyState равно 4, код проверяет свойство состояния (status), которое предоставляет код HTTP-ответа. Значение 200 указывает, что ответ был получен успешно; другие коды указывают на ту или иную ошибку (такую как отсутствующая страница, занятый веб-сервер и т.п.).

Следует отметить, что Ajax не избавляет от любых передач данных на сервер и обратно, и редко уменьшает время обработки запроса сервером. Реальная различие состоит в том, что полный цикл обмена происходит в фоновом режиме без вывода каких-либо сообщений, что придает приложению более отзывчивый вид.

Пройди тесты
Лучший чат для C# программистов