Применение AJAX в веб-приложениях с помощью ScriptManager (исходники)

Источник: MSDN Magazine
Бен Раш

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

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

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

ScriptManager (как серверный элемент управления) реагирует на события в жизненном цикле страницы ASP.NET и использует эти события для координации работы всех элементов управления, параметров и кода ASP.NET AJAX. ScriptManager подключается к конкретному событию, получает уведомление, когда происходит это событие, и настраивает ряд параметров в зависимости от среды; этот процесс повторяется несколько раз на протяжении цикла рендеринга страницы ASP.NET.

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

Начнем со сценариев как центральной части ASP.NET AJAX. По сути, вся функциональность ASP.NET AJAX зависит от соответствующих библиотек сценариев. Затем я опишу некоторые средства поддержки AJAX, способы взаимодействия с веб-сервисами и, наконец, кратко расскажу об аутентификации. При обсуждении каждой темы я покажу, как изменять параметры через ScriptManager.

Написание сценариев с помощью ScriptManager

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

  1. Зарегистрировать пространство имен в ASP.NET AJAX.
  2. Создать метод-конструктор.
  3. Создать прототип класса.
  4. Зарегистрировать класс в ASP.NET AJAX.
  5. Уведомить клиентский сценарий, добавленный ScriptManager, о конце определения типа (вызвать Sys.Application.notifyScriptLoaded).

Листинг 1. Определение класса в ASP.NET AJAX

Type.registerNamespace("ASPNet.Rocks.ButtonHandler");
ASPNet.Rocks.ButtonHandler = function(){
    return;
}
ASPNet.Rocks.ButtonHandler.prototype = {
    get_AlertMessage: function(){
        return this._alertMessage;
    }
    set_AlertMessage: function(value){
        this._alertMessage = value;
        return;
    }
    attachToElement: function(element, eventName){
        Sys.UI.DomEvent.addHandler(element, eventName,
            this.clickHandler);
        return;
    }
    clickHandler: function(eventSourceElement){
        alert(clickHandlerInstance.get_AlertMessage());
        return;
    }
}
ASPNet.Rocks.ButtonHandler.registerClass(
    "ASPNet.Rocks.ButtonHandler",null,Sys.IDisposable);
if(typeof(Sys)!=='undefined')
    Sys.Application.notifyScriptLoaded();

Данный класс предоставляет свою функциональность только клиенту. Однако с помощью элемента управления ScriptManager можно использовать гораздо более интересные возможности создания сценариев в ASP.NET AJAX, позволяющие предоставлять функции созданного класса как JavaScript-сценарию на клиентской стороне, так и коду Microsoft .NET Framework на сервере. Например, если потребитель вашего элемента управления установит свойство экземпляра следующим образом:

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        this.MyCustomButton.AlertMessage =
            "Hey, stop clicking me!";
    }
}
		

то интуитивно станет понятной возможность взаимодействия с данным экземпляром элемента управления еще и через сценарий в браузере, например так:

<script type="text/javascript">
    function get_text()
    {
        var mb = $find("MyCustomButton");
        var text = mb.get_AlertMessage();
        // Делаем что-то с текстом
    }
		</script>
		

Заметьте: я использую одно и то же имя MyCustomButton как ссылку на элемент управления и на сервере, и на клиенте; я также работаю со свойством AlertMessage, как если бы его значение без проблем пересекало границу между сервером и клиентом. Это кажется естественным, но до появления ASP.NET AJAX единая модель программирования сервера и клиента была труднодоступна и потребовала бы написания изрядного объема кода. Теперь эта унифицированная парадигма «сервер-клиент» интегрирована и полностью поддерживается в ASP.NET AJAX.

Тесная интеграция сервера и клиента обеспечивается двумя новыми интерфейсами: IScriptControl и IExtenderControl. Для использования этих интерфейсов унаследуйте ваш веб-элемент управления ASP.NET от них, примените требуемые методы интерфейса и зарегистрируйте элемент управления при помощи элемента управления ScriptManager страницы. Например, следующий каркас кода серверного элемента управления использует методы IScriptControl:

class CustomButton : Button, IScriptControl
{
    IEnumerable<ScriptReference>
        IScriptControl.GetScriptReferences()
    {
        ...
    }

    IEnumerable<ScriptDescriptor>
        IScriptControl.GetScriptDescriptors()
    {
        ...
    }
}
		

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

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

GetScriptDescriptors, с другой стороны, возвращает так называемые дескрипторы сценариев (script descriptors) - объекты ScriptDescriptor, описывающие свойства и события вашего клиентского класса. Можно сказать, что дескрипторы сценариев хранят метаданные вашего клиентского класса, в том числе свойств и сопоставленных с ними значений.

На лист. 2 показан более полный пример серверного элемента управления, вкратце обрисованного ранее. Здесь можно увидеть реализации методов GetScriptDescriptors и GetScriptReferences.

Листинг 2. Применение GetScriptDescriptors и GetScriptReferences

private string _alertMessage = null;

public string AlertMessage
{
    get { return _alertMessage; } set { alertMessage = value; }
}

public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
    ScriptControlDescriptor descriptor =
        new ScriptControlDescriptor(
        "CustomButton", this.ClientID);
    descriptor.AddProperty("AlertMessage", this._alertMessage);
    return new ScriptDescriptor[] { descriptor };
}

public IEnumerable<ScriptReference> GetScriptReferences()
{
    ScriptReference reference = new ScriptReference(
        "MyCustomContent.JScript1.js", "MyCustomContent");
    return new ScriptReference[] { reference };
}
			

В теле GetScriptDescriptors я создаю экземпляр объекта ScriptDescriptor. Конструктор для ScriptDescriptor принимает имя описываемого мной элемента управления, CustomButton. Затем я добавляю свойство AlertMessage и его значение (хранящееся в закрытом члене _alertMessage) в ScriptDescriptor. Свойство AlertMessage, с которым я хочу взаимодействовать из клиентского кода, теперь описано для ASP.NET AJAX.

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

Теперь я могу зарегистрировать элемент управления в ScriptManager, чтобы тот знал о его существовании (позже я расскажу об этом этапе подробнее). После создания страницы ScriptManager будет реагировать на различные события, о которых он получает уведомления, и вызывать GetScriptDescriptors и GetScriptReferences. Эти два метода затем возвратят элементу управления ScriptManager объекты ScriptDescriptor и ScriptReference со всей информацией, необходимой для подключения клиентского объекта к его эквиваленту на сервере. В приведенном примере ScriptDescriptor описывает пользовательский элемент управления CustomButton со свойством AlertMessage. Кроме того, в ScriptDescriptor будет включено значение свойства, установленное в GetScriptDescriptors.

ScriptManager также получит ссылку на внешний JS-файл MyCustomContent.JSScript1.js. Ссылка будет в виде объекта ScriptReference, возвращенного GetScriptReferences. Прототип клиентского класса для элемента управления в этом JS-файле определен так, как показано на лист.3.

Листинг 3. Прототип клиентского класса для элемента управления

CustomButton.prototype = {
    initialize : function() {
        // Инициализация базового класса
        CustomButton.callBaseMethod(this,'initialize');

        this._onclick =
            Function.createDelegate(this, this._onclick);

        Sys.UI.DomEvent.addHandler(
            this.get_element(), 'click', this._onclick);
    },
    dispose : function() {
        // Освобождение описателей
        $clearHandlers(this.get_element());

        CustomButton.callBaseMethod(this,'dispose');
    },
    get_AlertMessage: function(){
        return this._alertMessage;
    },
    set_AlertMessage: function(value){
        this._alertMessage = value;
        return;
    },
    _onclick: function(e){
        alert(this._alertMessage);
        return;
    }
}
CustomButton.registerClass("CustomButton", Sys.UI.Control);
if (typeof(Sys) !== 'undefined')
    Sys.Application.notifyScriptLoaded();
			

Первое, что бросается в глаза, - в этом клиентском классе есть методы (аксессоры) get_ и set_ для свойства AlertMessage. Элемент управления ScriptManager установит значение данного свойства в соответствии с указанным на сервере при помощи объекта ScriptDescriptor. Поскольку я предоставил AlertMessage в серверном коде, значение свойства, установленное на сервере, теперь будет отражаться в свойстве AlertMessage, доступном на клиентском объекте.

AJAX и ScriptManager

При первом знакомстве с ASP.NET AJAX многие разработчики начинают с элемента управления UpdatePanel. Если элемент управления ScriptManager включен в страницу и UpdatePanel содержит какие-нибудь элементы управления, то элементы в UpdatePanel можно асинхронно обновлять средствами AJAX. В окне дизайнера Visual Studio это обычно выглядит, как на рис. 1.

Рис. 1. Простой асинхронный элемент управления

Сложно представить что-то более простое. Если щелкнуть такой элемент управления Button, он вызовет событие обратной передачи, которое будет перехвачено UpdatePanel. После этого UpdatePanel заново отправит событие обратной передачи как частичную обратную передачу, и содержимое будет асинхронно обновлено (без полной перезагрузки страницы браузером).

Впрочем, есть много интересных ситуаций, при которых ожидания разработчика не сбываются, вынуждая его разгребать последствия. Вот пример нового и весьма любопытного способа разрыва ссылок сценариев при использовании UpdatePanel и AJAX. В прежние времена здесь пришлось бы добавлять сценарий к странице как результат полной обратной передачи, например:

protected void Button1_Click(object sender, EventArgs e)
{
    Page.ClientScript.RegisterStartupScript(
        this.GetType(),"myscript","alert('hello world!');");
}
		

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

Вместо этого, если нужно, чтобы кнопка вводила сценарий клиента в выводимые данные, следует использовать ScriptManager:

protected void Button1_Click(object sender, EventArgs e)
{
    ScriptManager.RegisterStartupScript(
        this, this.GetType(), "myscript",
        "alert('hello world!');", true);
}
		

Результат одинаков, но методы слегка отличаются. Фактически методы элемента управления ClientScriptManager теперь включены в элемент управления ScriptManager как статические (например, RegisterStartupScript). Теперь, если вы используете ASP.NET AJAX и хотите работать со сценариями через частичные обратные передачи, вам следует вызывать методы, предоставляемые ScriptManager.

В качестве еще одного «продвинутого» примера рассмотрим настройку элемента управления на рис. 2. В нормальных обстоятельствах щелчок элемента управления LinkButton приведет к полной обратной передаче веб-формы. Но что делать, если нужно щелкнуть LinkButton и вызвать тем самым асинхронное обновление UpdatePanel? Или, другими словами, щелкнуть LinkButton и заставить UpdatePanel вести себя так, будто LinkButton находится внутри него. Для этого надо использовать в элементе управления ScriptManager метод RegisterAsyncPostBackControl:

protected void Page_Load(object sender, EventArgs e)
{
    // Регистрируем LinkButton1 как элемент управления,
    // который может вызывать частичные обратные передачи
    ScriptManager1.RegisterAsyncPostBackControl(LinkButton1);
}
		

Рис. 2. Асинхронное использование Link Button

Теперь щелчок LinkButton заставит UpdatePanel обновиться асинхронно, как если бы LinkButton находился внутри UpdatePanel.

Как противоположный пример, элемент внутри UpdatePanel можно сделать вызывающим обновление всей страницы - полную обратную передачу. Для этого надо просто вызывать в ScriptManager метод RegisterPostBackControl:

protected void Page_Load(object sender, EventArgs e)
{
    // Button1 будет вызывать полную обратную передачу
    // независимо от того, где он находится на странице
    ScriptManager1.RegisterPostBackControl(Button1);
}

Этот код заставит элемент управления Button1 выполнять полную обратную передачу страницы, даже если Button1 находится внутри UpdatePanel.

Зайдем еще дальше. Только что было показано, как с помощью ScriptManager настраивать элементы управления, вызывающие событие частичной обратной передачи элемента управления UpdatePanel, но как быть, если нужно обновить элемент управления где-то на странице (за пределами UpdatePanel) при обновлении этого UpdatePanel? Элемент управления ScriptManager позволяет и такое.

Метод RegisterDataItem элемента ScriptManager упрощает обновление элементов управления или данных за пределами UpdatePanel. RegisterDataItem позволяет отправлять клиенту лишь выбранные дополнительные данные, когда UpdatePanel проводит обратную передачу; эти данные могут быть использованы сценарием, который пишется на клиенте.

Возьмем для примера ситуацию с элементами управления, показанную на рис. 3. Здесь мне нужно обновить элемент управления Label, присвоив ему значение, выбранное в элементе управления Calendar. Это может показаться несложным, но Calendar находится внутри UpdatePanel, а Label - нет. Как указать UpdatePanel обновить Label? Ответ прост: используя RegisterDataItem из серверного кода, можно добиться отправки дополнительных данных клиентскому коду. Клиент может задействовать данные, отправленные RegisterDataItem, и соответственно обновить Label:

protected void Calendar1_SelectionChanged(object sender,
    EventArgs e)
{
    ScriptManager1.RegisterDataItem(
        Label1, Calendar1.SelectedDate.ToShortDateString());
}
		

Рис. 3. Обновление элементов управления за пределами UpdatePanel

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

<script type="text/javascript">
    Sys.WebForms.PageRequestManager.getInstance().
        add_pageLoading(PageLoadingHandler);
    function PageLoadingHandler(sender,args){
        var dataItems = args.get_dataItems();
        if($get('Label1')!==null){
            $get('Label1').innerHTML = dataItems['Label1'];
        }
        return;
    }
</script>

Взгляните на код сценария. Он выполняет несколько действий. Во-первых, регистрируется на событие pageLoading через клиентский класс PageRequestManager. Во-вторых, реализует для этого события обработчик PageLoadingHandler. В-третьих, получает набор элементов данных «имя-значение» из второго параметра (args). Наконец, извлекает нужное вам значение, передавая предоставленное имя элемента управления в качестве первого аргумента метода RegisterDataItem на сервере.

Веб-сервисы и ScriptManager

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

Элемент управления ScriptManager играет роль своего рода глобального регистратора всех сервисов, необходимых вам в приложении ASP.NET AJAX. На рис. 4 показано меню свойств для ScriptManager в том виде, в каком оно отображается в Visual Studio.

Рис. 4. Свойства ScriptManager

Вероятно, вы заметили, что я выделил подсветкой набор Scripts; под ним находится набор Services (веб-сервисы). Любые сервисы, которые должны взаимодействовать с вашим клиентским кодом, нужно зарегистрировать через ScriptManager. Чтобы добавить ссылку на сервис в элемент управления ScriptManager, просто разверните набор Services и добавьте ссылку, как показано на рис. 5.

Рис. 5. Добавление ссылки на веб-сервис

Что именно при этом происходит? Все подробности скоро будут изложены, но при просмотре исходного кода для страницы, ссылающейся на сервис через ScriptManager, можно заметить нечто подобное приведенному ниже:

<script src="WebService.asmx/jsdebug"
    type="text/javascript"></script>

Эта внешняя ссылка была добавлена ScriptManager в вывод страницы, поскольку с ее помощью зарегистрирован веб-сервис. Заметьте, что к имени веб-сервиса добавлен префикс /jsdebug; если бы это была рабочая сборка, к имени был бы добавлен лишь префикс /js. Когда конвейер ASP.NET AJAX видит запрос к данному сервису с добавленным /js, он возвращает класс сценария, обертывающий методы веб-сервиса (это называется прокси-сценарием). Тело каждого метода в этом классе прокси-сценария выполняет асинхронный вызов соответствующего метода веб-сервиса. Следовательно, для вызова методов веб-сервиса достаточно вызвать соответствующий метод в классе сценария, созданном для вас инфраструктурой веб-сервисов в ASP.NET AJAX. Это действительно настолько просто.

Например, если сервис с именем WebService предоставляет метод Add примерно так:

[WebMethod]
public int Add(int a, int b) { return a + b; }
то его можно вызвать из сценария:
function CallAdd()
{
    // Метод немедленно возвращает управление,
    // вся обработка выполняется асинхронно
    WebService.Add(0, 6, OnMethodSucceeded, OnMethodFailed);
}
		

В этом примере автоматически созданный инфраструктурой класс прокси-сценария обертывает вызовы метода веб-сервиса, так что с ними можно взаимодействовать из сценария. WebService.Add будет таким образом вызывать соответствующий метод веб-сервиса - Add.

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

function OnMethodSucceeded(result, eventArgs)
{
    var label = Sys.UI.DomElement.getElementById('Label1');
    var sb = new Sys.StringBuilder();
    sb.append('You got back a value of ');
    sb.append(result.toString());
    label.innerHTML = sb.toString();
}

function OnMethodFailed(error)
{
    alert(error.get_message());
}
		

Запрос к веб-сервису отправляется и результат принимается асинхронно, так что конечный результат аналогичен использованию элемента управления UpdatePanels. Содержимое можно обновить с помощью данных, полученных от веб-сервиса, не вызывая при этом полной обратной передачи. В приведенном ранее примере кода я асинхронно обновил Label1, используя результаты вызова веб-сервиса, направленного веб-методу Add. Конечный результат может выглядеть примерно так, как показано на рис. 6. При этом полной обратной передачи не произойдет.

Рис. 6. Обновленная метка

Аутентификация и персонализация

Дополнительные возможности ASP.NET AJAX вроде аутентификации и персонализации также используют веб-сервисы. Служба аутентификации ASP.NET AJAX реализует два метода: один для входа пользователя в систему и второй для выхода пользователя из системы:

Sys.Services.AuthenticationService.login
Sys.Services.AuthenticationService.logout
		

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

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

<script type="text/javascript">
    function MyMethod(username, password)
    {
        Sys.Services.AuthenticationService.login(username,
            password,false,null,null,null,null,"User Context");
    }
</script>

Операции, необходимые для включения аутентификации в приложениях AJAX, хорошо документированы (см. ajax.asp.net), так что нет нужды рассказывать о них здесь. Но, как несложно заметить, после включения аутентификация на основе форм легко реализуется через сценарий.

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

[WebMethod]
public bool Login(string userName,
    string password, bool createPersistentCookie)
{
    ... // код для проверки учетных данных
        // пользователя и его входа
}

[WebMethod]
public void Logout()
{
    ... // код для выхода пользователя
}

Реализовав этот код, вы должны зарегистрировать новую службу аутентификации через ScriptManager. После регистрации любой код, ранее взаимодействовавший на клиенте с используемой по умолчанию службой аутентификации ASP.NET AJAX, начнет использовать новую службу вместо нее. Следовательно, со стороны клиентского сценария не потребуется изменений для работы с новым веб-сервисом аутентификации.

Ниже приведен пример декларативной регистрации службы аутентификации с помощью ScriptManager:

<asp:ScriptManager ID="ScriptManager1"
  runat="server" >
    <AuthenticationService
  Path="WebService.asmx" />
</asp:ScriptManager>

Не углубляясь пока в детали реализации, вы можете при работе с ASP.NET AJAX использовать преимущества собственных служб профилей. Службу профилей можно зарегистрировать через ScriptManager точно так же, как и службу аутентификации. Службы профилей позволяют подстроить веб-сайт под конкретного пользователя. Поскольку это делается из клиентского сценария, результаты будут интуитивно понятны пользователю и не вызовут затруднений. Примеры см. по ссылке ajax.asp.net.

Как все это работает

Жизненный цикл элемента управления ScriptManager делится на два основных этапа. На первом происходит проверка среды на предмет способности поддерживать все богатые возможности ASP.NET AJAX и установка любых компонентов, необходимых для такой поддержки. На втором этапе элемент управления осуществляет асинхронное взаимодействие с кодом на клиентском компьютере, позволяя сценарию выполнить необходимые обновления страницы. Поскольку этот элемент управления находится на серверной стороне, а веб-программирование в ASP.NET основано на событиях, главная функциональность ScriptManager - регистрация на события и реакция на них. Схема событий, на которые реагирует этот элемент, показана на рис. 7.

Рис. 7. Основные события ScriptManager

Регистрация объектов с помощью ScriptManager

С помощью ScriptManager можно декларативно добавлять ссылки на сценарии и сервисы. То же самое можно делать и программным способом, через свойства Services и Scripts в элементе управления ScriptManager. Кроме того, можно посылать специальные данные клиенту и создавать элементы управления для работы с ScriptManager в качестве особых элементов управления «сценарии» (script controls). Все эти средства требуют ссылки на объект с помощью ScriptManager.

Что именно ScriptManager делает со всеми этими ссылками? Когда страница, на которой размещены элемент управления ScriptManager и функциональность ASP.NET AJAX, впервые запрашивается браузером, на стадии инициализации дочерних элементов управления этой страницы запускается принадлежащий ScriptManager метод OnInit. Он выполняет ряд важных действий, в том числе проверяет, находится ли на странице только один элемент управления ScriptManager и не выполняет ли страница в данный момент частичную обратную передачу. Однако наиболее важная операция в OnInit - регистрация на серию событий, создаваемых страницей, таких как InitComplete, PreRenderComplete и PreRender. Элементу управления ScriptManager нужно знать, когда эти события происходят на родительской странице, поскольку ASP.NET AJAX работает, используя такие события страницы.

Наиболее важным для загрузки сценариев и ссылок на сервисы в браузер является событие PreRenderComplete. Обработчик этого события называется OnPagePreRenderComplete (лист. 4) и является методом самого элемента управления ScriptManager.

OnPagePreRenderComplete предназначен для обработки всех сценариев и сервисов, зарегистрированных с помощью ScriptManager, вплоть до появления события PreRenderComplete данной страницы (включая сценарии, необходимые элементам управления «сценарии», сценарии, на которых ссылаются элементы управления ScriptManagerProxy, и т. д.). Если страница не выполняет в этот момент частичную обратную передачу, регистрируется блок сценария глобализации вместе со всеми имеющимися сценариями и сервисами. С другой стороны, если частичная обратная передача находится в процессе выполнения, то регистрируются лишь сценарии. Сценарии должны быть зарегистрированы как для частичной, так и для полной обратной передачи, поскольку ScriptManager позволяет добавлять сценарий в любой момент.

На данном этапе регистрируются все ваши сценарии и сервисы, но что это означает на практике? Как регистрация сценария или ссылки отражается на странице?

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

RegisterScripts будет вызван и в ходе асинхронной обратной передачи (из-за раздела else, показанного на лист. 4). Этот метод по-прежнему обращается к ClientScriptManager, но, поскольку тот не умеет работать с асинхронной обратной передачей, при этом ничего не произойдет. Вместо этого RegisterScripts вызовет внутренний метод RegisterScriptIncludeInternal, который сохранит ссылку на сценарий во внутреннем массиве для последующего использования.

Листинг 4. Обработчик OnPagePreRenderComplete в ScriptManager

			private void OnPagePreRenderComplete(object sender,
    EventArgs e)
{
    if (!IsInAsyncPostBack)
    {
        if (SupportsPartialRendering)
        {
            IPage.ClientScript.GetPostBackEventReference(
                new PostBackOptions(this, null, null, false,
                false, false, false, true, null));
        }

        RegisterGlobalizationScriptBlock();
        RegisterScripts();
        RegisterServices();
    }
    else RegisterScripts();
}
			

А как насчет ссылок на веб-сервисы? Следует вспомнить, что при добавлении ссылки на веб-сервис к элементу управления ScriptManager в сценарий добавляется ссылка. Эта ссылка заставит браузер сделать запрос по указанному URL. Инфраструктура ASP.NET AJAX проверит класс, применяющий вышеупомянутый веб-сервис, и вернет класс прокси-сценария, который можно использовать из клиентского кода. Браузер загрузит этот автоматически созданный класс прокси, который и станет ссылкой на веб-сервис.

Чтобы создать ссылку в сценарии на автоматически создаваемый класс прокси, RegisterScripts перебирает все имеющиеся ссылки на веб-сервисы и в конечном счете вызывает следующий метод:

private string GetProxyPath(Control containingControl,
    bool debug)
{
    if (debug)
        return GetServicePath(containingControl, true) +
            RestHandlerFactory.ClientDebugProxyRequestPathInfo;
    else
        return GetServicePath(containingControl, true) +
            RestHandlerFactory.ClientProxyRequestPathInfo;
}

Ссылка в сценарии, созданная GetProxyPath, будет добавлена на страницу точно так же, как и обычные ссылки - использованием ClientScriptManager для первого запроса страницы и сохранения ссылки в некоем внутреннем массиве.

Что представляет собой этот внутренний массив? На самом деле это несколько массивов. Вспомним, что при первом запросе страницы ScriptManager полагается на традиционное добавление ссылок в класс ClientScriptManager для данной страницы. Но ClientScriptManager не работает при частичной обратной передаче. Вместо этого метода ScriptManager должен переключиться на какой-то другой, и внутренние массивы предназначены как раз для этого. Ответ на частичную обратную передачу, как можно видеть, представляет собой синтаксически правильный, легко анализируемый блок данных, который проверяется и используется клиентской инфраструктурой. Вызывается событие Render элемента управления ScriptManager, что в свою очередь приводит к вызову метода ProcessScriptRegistration класса PageRequestManager (лист. 5).

Листинг 5. Метод ProcessScriptRegistration класса PageRequestManager

private void ProcessScriptRegistration(HtmlTextWriter writer)
{
    owner.ScriptRegistration.RenderActiveArrayDeclarations(
        updatePanelsToRefresh, writer);
    owner.ScriptRegistration.RenderActiveScripts(
        updatePanelsToRefresh, writer);
    owner.ScriptRegistration.RenderActiveSubmitStatements(
        updatePanelsToRefresh, writer);
    owner.ScriptRegistration.RenderActiveExpandos(
        updatePanelsToRefresh, writer);
    owner.ScriptRegistration.RenderActiveHiddenFields(
        updatePanelsToRefresh, writer);
    owner.ScriptRegistration.RenderActiveScriptDisposes(
        updatePanelsToRefresh, writer);
}
			

Именно на этом этапе ссылка сценария будет преобразована в вывод страницы для ASP.NET AJAX. Каждый метод получает HtmlTextWriter, переданный ему для этапа рендеринга, и записывает в него необходимый контент. И каждый метод (например, RenderActiveScripts) будет ссылаться на соответствующий ему внутренний массив, заполненный ранее.

AJAX в ASP.NET

При помощи прокси отладчика Fiddler HTTP (fiddlertool.com/fiddler) можно отследить весь веб-трафик, проходящий через браузер Internet Explorer на вашем компьютере. Откройте примеры AJAX на ajax.asp.net и посмотрите в Fiddler, что происходит; вы увидите, что при каждом событии частичной обратной передачи выдается HTTP POST. Данный HTTP POST содержит обычные HTTP-заголовки, но включает и один заголовок, известный далеко не всем:

x-microsoftajax: Delta=true

Этот заголовок является ключом. После того как ScriptManager распознает этот заголовок, вместо пассивной вставки ссылок в вывод страницы, он анализирует данные, переданные в виде POST, и возвращает ответ клиенту в формате, понятном клиентскому сценарию.

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

Этот клиентский сценарий записывается в вывод страницы методом RenderPageRequestManagerScript. Я убрал из него большую часть инфраструктурного кода, чтобы облегчить чтение; его основная часть, как правило, выглядит так:

internal void RenderPageRequestManagerScript(
    HtmlTextWriter writer)
{
    ...
    writer.Write(_owner.UniqueID);
    ...
    writer.Write(_owner.IPage.Form.ClientID);
    ...
    RenderUpdatePanelIDsFromList(writer, _allUpdatePanels);
    ...
    writer.Write(GetAsyncPostBackControlIDs(true));
    ...
    writer.Write(GetPostBackControlIDs(true));
    ...
}
		

Заметьте, что в страницу записываются идентификаторы UpdatePanels, PostBackControl и AsyncPostBackControl; все это элементы управления, которые должны участвовать в работе ASP.NET AJAX. Клиентский PageRequestManager отвечает за отслеживание всех событий, созданных элементами управления, зарегистрированными с помощью ScriptManager; метод RenderPageRequestManagerScript инициализирует PageRequestManager, работающий на клиенте с теми элементами управления, за которыми он должен наблюдать.

Когда на клиенте запускается событие обратной передачи, PageRequestManager проверяет, было ли оно вызвано одним из элементов управления, идентифицируемых сценарием, записанным в RenderPageRequestManagerScript. Если да, PageRequestManager отменяет событие обратной передачи и заново упаковывает его данные. Эти данные события обратной передачи будут потом переданы на сервер через клиентский класс Sys.Net.WebRequest (открытый класс, который можно использовать из клиентского кода). В запросе POST, отправляемом на сервер через Sys.Net.WebRequest, будет установлен заголовок x-microsoftajax: Delta=true.

На серверной стороне теперь создан и загружен в дерево элементов управления страницы экземпляр элемента управления ScriptManager. Как серверный элемент управления, ScriptManager будет уведомляться о данных в форме POST через LoadPostData - ASP.NET-метод, позволяющий отдельным элементам управления выбирать из формы POST относящиеся к ним данные. (Это стандартное событие, не уникальное для ASP.NET AJAX.) Диаграмма событий на рис. 10 показывает, что событие LoadPostData происходит сразу после InitComplete на странице. Внутри обработчика LoadPostData элемент управления ScriptManager определяет элементы управления, породившие форму POST (включая UpdatePanel, в котором находится элемент управления, если таковой есть). Идентификатор элемента управления, вызвавшего событие обратной передачи, сохраняется для последующего использования.

К этому моменту ScriptManager обнаруживает, что происходит частичная обратная передача, и определяет вызвавшие ее элементы управления. Теперь можно создать ответ клиенту. ScriptManager полностью переписывает метод Render, используемый по умолчанию страницей, на которой размещен этот элемент управления, беря на себя рендеринг страницы ASP.NET. Следует отметить, что до сих пор ScriptManager использовался как простое средство для изменения различных параметров в приложении ASP.NET AJAX. Теперь вы наконец можете убедиться, что ScriptManager фактически становится новым классом страницы со своим форматом рендеринга элементов управления в поток вывода страницы.

К этому моменту ScriptManager обнаруживает, что происходит частичная обратная передача, и определяет вызвавшие ее элементы управления. Теперь можно создать ответ клиенту. ScriptManager полностью переписывает метод Render, используемый по умолчанию страницей, на которой размещен этот элемент управления, беря на себя рендеринг страницы ASP.NET. Следует отметить, что до сих пор ScriptManager использовался как простое средство для изменения различных параметров в приложении ASP.NET AJAX. Теперь вы наконец можете убедиться, что ScriptManager фактически становится новым классом страницы со своим форматом рендеринга элементов управления в поток вывода страницы.

Также стоит отметить, что в переопределении рендеринга объектов Form элемент ScriptManager обрабатывает и отправляет клиенту все дополнительные данные, зарегистрированные вами в этом элементе вызовом RegisterDataItem. Поэтому важно быть готовым отправить эти данные клиенту к моменту, когда от объекта Form потребуется рендеринг на клиенте. Данные, предназначенные для отсылки клиенту, будут закодированы в неструктурированном формате или в формате JSON (JavaScript Object Notation).

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

Следуйте сценарию

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

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

Сценарии и сервисы должны быть зарегистрированы в ScriptManager в ходе обработки события PreRenderComplete страницы. Ссылка на сценарий может быть добавлена на страницу в течение как полной, так и частичной обратной передачи. ScriptManager требует от прочих объектов передать ему все их ссылки на сценарии и сервисы при регистрации сценариев.

Наконец, ASP.NET AJAX реализуется на клиенте путем переопределения исходного механизма рендеринга страницы. ScriptManager берет на себя контроль над циклом рендеринга страницы и передает клиенту ответ, который последний может легко проанализировать и использовать для обновления элементов в браузере. За счет переопределения рендеринга страницы клиенту могут передаваться лишь нужные элементы управления в специализированном формате с дополнительными данными, зарегистрированными в ScriptManager.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=8726