Клиентские библиотеки ASP.NET AJAX

86

До сих пор в большинстве ситуаций применялись функции верхнего уровня, предоставляемые платформой ASP.NET AJAX. Мы начали с рассмотрения поддержки веб-служб, после чего исследовали основные серверные элементы управления, такие как UpdatePanel, Timer и UpdateProgress. В ходе этого вы ознакомились с несколькими нюансами стороны клиентской части модели ASP.NET AJAX - например, с псевдонимом $get и событиями PageRequestManager. Тем не менее, внутренняя инфраструктура осталась без внимания.

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

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

Модель клиента

Основным строительным блоком ASP.NET AJAX являются клиентские библиотеки JavaScript. Они служат своего рода "клеем", который скрепляет все остальные компоненты. На рисунке ниже показано место клиентских библиотек в высокоуровневой модели ASP.NET AJAX:

Архитектура ASP.NET AJAX

Клиентские библиотеки привносят в мир JavaScript определенные черты среды .NET. Они состоят из трех основных частей:

Расширения JavaScript

Эти расширения предоставляют способ применения объектно-ориентированных технологий в сочетании с обыкновенным кодом JavaScript.

Базовые классы JavaScript

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

Платформа пользовательского интерфейса (UI Framework)

Эта платформа основана на инфраструктуре, созданной базовыми классами. Платформа UI Framework привносит понятия клиентских элементов управления и клиентской страницы.

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

Объектно-ориентированное программирование в JavaScript

JavaScript не является настоящим объектно-ориентированным языком, поскольку в нем отсутствует поддержка базовых объектно-ориентированных возможностей, таких как наследование и интерфейсы. Однако JavaScript часто называют языком, основанным на объектах, потому что JavaScript предоставляет встроенные объекты (для окна браузера, текущего HTML-документа и т.д.).

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

Создать одноразовый объект с любым выбранным набором свойств достаточно легко. Для этого сначала создается обыкновенный объект (с помощью ключевого слова var), а затем - свойства за счет присваивания им требуемых значений. Например, в следующем коде создается объект сотрудника с двумя присоединенными строковыми переменными - FirstName и LastName:

var emp = new Object;
emp.FirstName = "Ivan";
emp.LastName = "Petrov";

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

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

Чтобы создать более стандартизированное определение объекта, разработчики JavaScript обычно прибегают к одному из двух приемов: замыканиям или прототипам.

Замыкания

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

Модель замыкания проще всего понять, рассмотрев пример. Вот как выглядит код замыкания, которое определяет класс Employee, содержащий имя и фамилию сотрудника:

function Employee(first, last) {
    // Приватный раздел
    var _firstName = first;
    var _lastName = last;

    // Общедоступный раздел
    this.set_FirstName = function (first) {
        _firstName = first;
    }

    this.get_FirstName = function () {
        return _firstName;
    }

    this.set_LastName = function (last) {
        _lastName = last;
    }

    this.get_LastName = function () {
        return _lastName;
    }
}

Переменные, определяемые в замыкании (в этом примере _firstName и _lastName) локальны для функции Employee() и не могут быть доступны за ее пределами. С другой стороны, методы (в этом примере set_FirstName(), get_FirstName() и т.д.) могут быть вызваны в любом месте.

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

window.onload = function () {
    var emp = new Employee("Ivan", "Petrov");
    var name = emp.get_FirstName() + ' ' + emp.get_LastName();
    alert(name);
}

Первая строка создает переменную emp и устанавливает ее так, чтобы она содержала ссылку на функцию Employee(). Другими словами, экземпляр объекта в действительности является всего лишь указателем на функцию, указывающим на конструктор, который создает объект. Результат можно видеть на рисунке ниже:

Создание специального объекта в JavaScript

В ранних сборках ASP.NET AJAX для реализации объектно-ориентированного программирования использовались замыкания. Однако в последующих сборках стала применяться система прототипов.

Формально функция Employee() обеспечивает себя четырьмя новыми свойствами: set_FirstName, get_FirstName, set_LastName и get_LastName. Каждому свойству она назначает функцию. Это позволяет вызывать каждое свойство, как если бы оно было методом. Другими словами, записывая emp.get_FirstName(), вы обращаетесь к свойству get_FirstName(), которое фактически является функцией.

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

Прототипы

Другим подходом, применяемым разработчиками для определения классов в JavaScript, являются прототипы. По ряду технических соображений прототипы являются предпочтительной технологией в ASP.NET AJAX. Прототипы обеспечивают более высокую производительность в некоторых браузерах (таких как Firefox и Google Chrome) и они предоставляют лучшую поддержку для рефлексии, IntelliSense и отладки. Эти различия обусловлены тем, что члены прототипов являются встроенными внутрь, в то время как замыкания создают свои члены при каждом создании экземпляра объекта.

При использовании прототипа полагаются на общедоступное свойство prototype, которое имеет каждый объект JavaScript. Это свойство представляет общедоступный интерфейс объекта. Чтобы добавить в объект публично вызываемые методы, прототипу понадобится присвоить новые свойства.

Ниже приведен измененный код, в котором объект Employee определяется с помощью прототипа:

Employee = function (first, last)
{
    // Приватный раздел
    this._firstName = first; 
    this._lastName = last;
}

// Общедоступный раздел
Employee.prototype.set_FirstName = function(first) { 
    this._firstName = first;
}

Employee.prototype.get_FirstName = function() { 
    return this._firstName;
}

Employee.prototype.set_LastName = function (last) {
    this._lastName = last;
}

Employee.prototype.get_LastName = function () {
    return this._lastName;
}

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

В способе работы замыканий и прототипов существуют тонкие различия. По сути, замыкание создает специализированные члены объекта (set_FirstName, get_FirstName и т.д.) при каждом создании нового объекта на основе этого замыкания. Но при прототипном подходе, объект прототипа создается и конфигурируется только один раз, а затем копируется в каждый новый объект. Именно этим объясняется повышение производительности в некоторых браузерах.

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

Регистрация классов в ASP.NET AJAX

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

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

Employee = function (first, last) {
    // ...
}

// Общедоступный раздел
Employee.prototype.set_FirstName = ...

// ...

Employee.registerClass("Employee");

Помните, что формально переменная Employee является ссылкой на функцию конструктора, которая используется для создания объектов сотрудников. Вызов метода registerClass() возможен потому, что клиентские библиотеки ASP.NET AJAX добавляют методы для регистрации классов, пространств имен, интерфейсов и перечислений.

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

var emp = new Employee("Ivan", "Petrov");
alert(Object.getTypeName(emp));

При использовании этого кода с экземпляром незарегистрированного класса будет отображаться имя класса Object. Однако если класс Employee был зарегистрирован, отобразится более точное имя - Employee.

Класс Object предоставляет еще несколько членов, которые можно применять для получения информации о типе из зарегистрированного специального класса, в том числе implementsInterface (для проверки того, что реализует ли класс конкретный интерфейс), getInterfaces (для выяснения всех интерфейсов, реализуемых классом), inheritsFrom (для проверки того, унаследован ли данный класс от указанного класса, непосредственно или косвенно) и isInstanceOfType (для проверки того, является ли объект экземпляром указанного класса или класса, унаследованного от него).

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

Базовые типы

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

Расширенные типы JavaScript в ASP.NET AJAX
Тип Описание
Array

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

Boolean

Добавляет метод parse(), который позволяет преобразовывать строковое представление булевского типа в сам булевский тип

Date

Добавляет методы форматирования и синтаксического анализа, которые позволяют преобразовывать дату в и из строкового представления, используя либо инвариантное представление, либо соответствующее представление для текущей локали

Number

Добавляет методы форматирования и синтаксического анализа, которые позволяют преобразовывать числовое значение в и из строкового представления, используя либо инвариантное представление, либо соответствующее представление для текущей локали

String

Добавляет небольшой набор методов манипулирования строками для усечения строк и сравнения начала или конца строки с другой строкой. (Класс Sys.StringBuilder добавляет еще один способ создания строк.)

Error

Добавляет ряд свойств для обычных типов ошибок, которые возвращают соответствующие объекты исключений. Например, Error.argument возвращает объект Sys.ArgumentException

Object

Добавляет методы getType() и getTypeName(), которые служат отправными точками для отражения информации о типе (как было продемонстрировано в предыдущем разделе)

Function

Добавляет методы для управления классами, включая методы для определения пространств имен, классов и интерфейсов, как показано в последующих разделах

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

Пространства имен

Традиционно все функции JavaScript существуют в одном глобальном пространстве имен. Однако ASP.NET AJAX добавляет возможность выделить функции, которые представляют классы, в отдельные логические пространства имен. Это особенно полезно для предотвращения любого конфликта между вашим классом и встроенными классами ASP.NET AJAX.

Для регистрации пространства имен необходимо вызвать метод Type.registerNamespace() перед созданием своего класса. Затем тип нужно поместить в пространство имен с использованием полностью квалифицированного имени (вроде Business.Employee). Например:

<script>
    Type.registerNamespace("Business");

    Business.Employee = function (first, last) {
        // Приватный раздел
        this._firstName = first;
        this._lastName = last;
    }

    // Общедоступный раздел
    Business.Employee.prototype.set_FirstName = function (first) {
        this._firstName = first;
    }

    Business.Employee.prototype.get_FirstName = function () {
        return this._firstName;
    }

    Business.Employee.prototype.set_LastName = function (last) {
        this._lastName = last;
    }

    Business.Employee.prototype.get_LastName = function () {
        return this._lastName;
    }

    Business.Employee.registerClass("Business.Employee");

    window.onload = function () {
        var emp = new Business.Employee("Ivan", "Petrov");
        alert(Object.getTypeName(emp));
    }
</script>

Если теперь вызвать метод Object.getTypeName(), то получится полностью квалифицированное имя класса.

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

<script>
    Type.registerNamespace("Business");

    Business.Employee = function (first, last) {
        ...
    }
    
    Business.Employee.prototype.set_FirstName = ...

    Business.Employee.prototype = {
        set_FirstName: Business$Employee$set_FirstName, 
        get_FirstName: Business$Employee$get_FirstName, 
        set_LastName: Business$Employee$set_LastName,
        get_LastName: Business$Employee$get_LastName
    };
</script>

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

Наследование

ASP.NET AJAX предлагает также поддержку для создания классов, унаследованных от других классов. При регистрации производного класса в качестве второго аргумента нужно указать имя базового класса. Ниже приведен пример создания класса SalesEmployee, унаследованного от Employee. Для того чтобы это работало, класс Employee должен быть определен ранее в блоке сценария (или в предыдущем блоке сценария):

Business.SalesEmployee = function (first, last, salesDepartment) {
    // Вызвать конструктор базового класса для инициализации
    // данных родительского класса. Конструктор базового класса принимает
    // два параметра, представляющие имя и фамилию
    Business.SalesEmployee.initializeBase(this, [first, last]);

    // Инициализировать данные производного класса
    this._salesDepartment = salesDepartment;
}

Business.SalesEmployee.prototype.get_SalesDepartment = function () {
    return this._salesDepartment
}

Business.SalesEmployee.registerClass("Business.SalesEmployee", Business.Employee);

В вызове registerClass() передается имя нового класса (в виде строки) и имя родительского класса (в виде ссылки на функцию родительского класса). При такой регистрации класса он получает все элементы родительского класса, наряду с собственными элементами. Таким образом, можно устанавливать и получать информацию о подразделении, имени и фамилии из любого объекта SalesEmployee:

window.onload = function () {
        var emp = new Business.SalesEmployee("Ivan", "Petrov", "Western");

        var description = emp.get_FirstName() + " " + emp.get_LastName() + ": "
            + emp.get_SalesDepartment();

        alert(description);
}

Если производный класс предоставляет член с тем же именем, что и родительский класс, версия из родительского класса автоматически переопределяется. В отличие от языка C#, нет никакого способа создания членов, которые должны быть переопределены, или предотвращения переопределения членов класса. Кроме того, у производного класса есть доступ ко всем переменным, которые определяются в родительском классе (хотя следует избегать обращения к ним непосредственно, а применять вместо этого методы доступа к свойствам).

Единственной примечательной особенностью кода SalesEmployee является вызов метода initializeBase(), который позволяет конструктору обращаться к конструктору базового класса, чтобы он мог инициализировать имя и фамилию. Метод initializeBase() - один из членов, которые ASP.NET AJAX добавляет к базовому типу функции. Наряду с initializeBase() можно использовать метод callBaseMethod(), чтобы инициировать метод, который присутствует в базовом классе, но переопределен в производном классе.

Интерфейсы

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

Первым делом, конструктор интерфейса не должен содержать никакого кода и не должен присваивать никаких данных. Вместо этого он должен просто генерировать исключение NotImplementedException для предотвращения создания его экземпляров. Аналогично, члены, которые определены в прототипе, не должны содержать никакого кода и должны генерировать исключение NotImplementedException при их вызове. Это требование делает определения интерфейсов JavaScript несколько более длинными, чем определения интерфейсов C#.

Проще всего разобраться в модели интерфейса ASP.NET AJAX - рассмотреть один из интерфейсов, определенных в ASP.NET AJAX. Интерфейс Sys.IDisposable предоставляет эквивалент ASP.NET AJAX интерфейса System.IDisposable в .NET, который дает объектам способ немедленного освобождения используемых ими ресурсов. Интерфейс Sys.IDisposable определяет единственный метод по имени dispose().

Ниже приведен полный код интерфейса IDisposable:

Type.registerNamespace('Sys.UI');
Sys.IDisposable = function Sys$IDisposable() {
    throw Error.notImplemented();
}

function Sys$IDisposable$dispose() {
    throw Error.notImplemented();
}

Sys.IDisposable.prototype = {
    dispose: Sys$IDisposable$dispose
}

Sys.IDisposable.registerInterface('Sys.IDisposable');

Для регистрации интерфейса вместо метода registerClass() применяется метод registerInterface(). Перед использованием интерфейса следует удостовериться, что класс включает члены с требуемыми именами:

Business.SalesEmployee.prototype.dispose = function () {
    alert("Освобождение ресурсов");
}

Если это так, интерфейс можно реализовать, изменяя способ регистрации своего класса. При вызове метода registerClass() достаточно просто указать интерфейс, который требуется реализовать, в качестве третьего аргумента:

Business.SalesEmployee.registerClass("Business.SalesEmployee", 
     Business.Employee, Sys.IDisposable);
}

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

Ниже приведен фрагмент кода, который можно использовать для тестирования поведения по освобождению ресурсов. Когда объект SalesEmployee будет освобожден, отобразится соответствующее сообщение:

window.onload = function () {
        var emp = new Business.SalesEmployee("Ivan", "Petrov", "Western");
        emp.dispose();
}

Платформа веб-страниц

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

Класс приложения

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

Объект Application генерирует два ключевых события. Событие load происходит после того, как страница впервые обработана в браузере, и после каждой обратной отправки, включая асинхронные обратные отправки. Событие unload происходит, когда пользователь переходит к новой странице. Чтобы обработать эти события, понадобится добавить JavaScript-функции со следующими именами:

function pageLoad() {
    alert('Страница загружена');
}

function pageUnload() {
    alert('Переход к новой странице');
}

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

Класс Application предоставляет также событие init, которое возникает после того, как все сценарии были загружены для страницы, но перед созданием ее объектов. Событие init инициируется только однажды, при первой обработке страницы, но не происходит после асинхронных обратных отправок. Для присоединения к событию init обработчика служит метод Application.add_init() .Компоненты ASP.NET AJAX реагируют на событие init, чтобы создать клиентские элементы управления.

Класс PageRequestManager

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

Класс PageRequestManager инициирует ряд событий, на которые можно реагировать с помощью клиентского кода JavaScript. Эти события перечислены в таблице ниже. В предыдущих примерах PageRequestManager применялся для обработки ошибок асинхронного обратного вызова элементом управления UpdatePanel (за счет обработки endRequest) и реализации отмены элементом управления UpdateProgress (посредством обработки initializeRequest).

События PageRequestManager
Событие Описание
initializeRequest

Происходит до начала асинхронной обратной отправки. В этот момент обратную отправку можно отменить с помощью свойства Cancel объекта Sys.WebForms.InitializeRequestEventArgs, переданного обработчику события

beginRequest

Происходит перед запросом асинхронной обратной отправки (но после initializeRequest). В этот момент можно инициализировать индикаторы ожидания на странице (например, запустить подходящую анимацию). Это событие предоставляет объект Sys.WebForms.BeginRequestEventArgs, который можно использовать для выяснения того, какой элемент вызвал обратную отправку

pageLoading

Происходит после получения ответа на асинхронный обратный вызов, но до обновления страницы. В этот момент можно удалить индикаторы ожидания. Это событие предоставляет объект Sys.WebForms.PageLoadingEventArgs с информацией о панелях, которые будут обновлены в результате ответа на асинхронную обратную отправку

pageLoaded

Происходит после получения ответа на асинхронный обратный вызов и обновления страницы. Это событие предоставляет объект Sys.WebForms.PageLoadedEventArgs, который подробно описывает, какие панели были обновлены и созданы

endRequest

Происходит после обработки асинхронного ответа (после события pageLoaded) или во время обработки ответа в случае ошибки. В этот момент можно выполнить проверку на наличие ошибки и обеспечить специализированное уведомление об ошибке. Это событие предоставляет объект Sys.WebForms.End-RequestEventArgs, который подробно описывает произошедшую ошибку

Клиентский элемент управления AJAX

Полное описание платформы веб-страницы выходит за рамки этой статьи. Чтобы больше узнать о клиентской модели, обратитесь к документации ASP.NET AJAX Roadmap. Однако можно узнать очень много нового, рассмотрев краткий пример из интенсивного курса. В этом разделе будет продемонстрирован один из примеров из документации по ASP.NET AJAX: клиентская кнопка, которая обновляет свой внешний вид, когда курсор мыши перемещается над ней. В целях лучшей организации весь код этой кнопки размещен в отдельном JavaScript-файле HoverButton.js.

Для создания этого элемента управления применяется показанная ранее модель с прототипом. Создание начинается с регистрации пространства имен, затем определяется конструктор для элемента управления (с приватными данными), а после этого - общедоступный интерфейс, использующий свойство prototype. В этом примере класс назван HoverButton, и он представляет события, которые инициируются при щелчке на кнопке, перемещении курсора мыши над ней и смещении курсора мыши с кнопки.

Общая структура кода имеет следующий вид:

Type.registerNamespace("CustomControls");

// Определить конструктор
CustomControls.HoverButton = function (element) {

    CustomControls.HoverButton.initializeBase(this, [element]);

    this._clickDelegate = null;
    this._hoverDelegate = null;
    this._unhoverDelegate = null;
}

CustomControls.HoverButton.prototype = {

    ...
}

CustomControls.HoverButton.registerClass('CustomControls.HoverButton', Sys.UI.Control);

Обратите внимание, что специальные элементы управления должны всегда начинать код своего конструктора с вызова метода initializeBase(), который запускает конструктор базового класса Control.

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

Для присоединения и отсоединения обработчиков событий в JavaScript служат методы addHandler() и removeHandler(). Код реализации имеет следующий вид:

CustomControls.HoverButton.prototype = {

    // Свойства
    get_text: function () {
        return this.get_element().innerHTML;
    },
    set_text: function (value) {
        this.get_element().innerHTML = value;
    },

    // Привязка событий щелчка
    add_click: function (handler) {
        this.get_events().addHandler('click', handler);
    },
    remove_click: function (handler) {
        this.get_events().removeHandler('click', handler);
    },

    // Привязка наведения мыши
    add_hover: function (handler) {
        this.get_events().addHandler('hover', handler);
    },
    remove_hover: function (handler) {
        this.get_events().removeHandler('hover', handler);
    },
    
    // Привязка события выхода мыши из кнопки
    add_unhover: function (handler) {
        this.get_events().addHandler('unhover', handler);
    },
    remove_unhover: function (handler) {
        this.get_events().removeHandler('unhover', handler);
    },
    
    dispose: function () {... },
    initialize: function () {...},
    // ...
}

Прототип класса HoverButton содержит еще два метода: initialize(), который вызывается автоматически при создании объекта HoverButton, и dispose(), который вызывается при его освобождении.

Метод initialize() устанавливает связь между специальными событиями, которые определены в классе HoverButton, и событиями JavaScript, которые существуют в странице. Например, в следующем коде событие hover устанавливается так, чтобы оно инициировалось при наведении курсора мыши на кнопку или при передаче фокуса кнопке:

var element = this.get_element();

if (this._hoverDelegate === null) {
      this._hoverDelegate = Function.createDelegate(this, this._hoverHandler);
}

Sys.UI.DomEvent.addHandler(element, 'mouseover', this._hoverDelegate);
Sys.UI.DomEvent.addHandler(element, 'focus', this._hoverDelegate);

В соответствии с этим кодом делегат hoverHandler должен быть инициирован при наступлении события mouseover или focus для клиентского элемента. Делегат hoverHandler определен в конце прототипа. Он просто запускает связанный обработчик событий, как показано ниже:

_hoverHandler: function (event) {
        var h = this.get_events().getHandler('hover');
        if (h) h(this, Sys.EventArgs.Empty);
}

И, наконец, метод initialize() вызывает базовый метод initialize() в классе Control. Задача метода dispose() проще. Он просто проверяет существование обработчиков событий, и удаляет их, если те существуют. Ниже показана оставшаяся часть класса HoverButton:

CustomControls.HoverButton.prototype = {
    // ...
    
    dispose: function () {

        var element = this.get_element();

        if (this._clickDelegate) {
            Sys.UI.DomEvent.removeHandler(element, 'click', this._clickDelegate);
            delete this._clickDelegate;
        }

        if (this._hoverDelegate) {
            Sys.UI.DomEvent.removeHandler(element, 'focus', this._hoverDelegate);
            Sys.UI.DomEvent.removeHandler(element, 'mouseover', this._hoverDelegate);
            delete this._hoverDelegate;
        }

        if (this._unhoverDelegate) {
            Sys.UI.DomEvent.removeHandler(element, 'blur', this._unhoverDelegate);
            Sys.UI.DomEvent.removeHandler(element, 'mouseout', this._unhoverDelegate);
            delete this._unhoverDelegate;
        }
        CustomControls.HoverButton.callBaseMethod(this, 'dispose');
    },

    initialize: function () {

        var element = this.get_element();

        if (!element.tabIndex) element.tabIndex = 0;

        if (this._clickDelegate === null) {
            this._clickDelegate = Function.createDelegate(this, this._clickHandler);
        }
        Sys.UI.DomEvent.addHandler(element, 'click', this._clickDelegate);

        if (this._hoverDelegate === null) {
            this._hoverDelegate = Function.createDelegate(this, this._hoverHandler);
        }
        Sys.UI.DomEvent.addHandler(element, 'mouseover', this._hoverDelegate);
        Sys.UI.DomEvent.addHandler(element, 'focus', this._hoverDelegate);

        if (this._unhoverDelegate === null) {
            this._unhoverDelegate = Function.createDelegate(this, this._unhoverHandler);
        }
        Sys.UI.DomEvent.addHandler(element, 'mouseout', this._unhoverDelegate);
        Sys.UI.DomEvent.addHandler(element, 'blur', this._unhoverDelegate);

        CustomControls.HoverButton.callBaseMethod(this, 'initialize');

    },
    _clickHandler: function (event) {
        var h = this.get_events().getHandler('click');
        if (h) h(this, Sys.EventArgs.Empty);
    },
    _hoverHandler: function (event) {
        var h = this.get_events().getHandler('hover');
        if (h) h(this, Sys.EventArgs.Empty);
    },
    _unhoverHandler: function (event) {
        var h = this.get_events().getHandler('unhover');
        if (h) h(this, Sys.EventArgs.Empty);
    }
}

И последний нюанс. Сценарий должен уведомлять класс Application о достижении конца его кода. Для этого в конец страницы понадобится добавить следующий оператор кода:

if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

Это не обязательно, если код сценария размещен в странице или встроен в сборку. В этих случаях метод notifyScriptLoaded() вызывается автоматически. Но в рассматриваемом примере код JavaScript размещен в файле HoverButton.js, поэтому такая строка необходима.

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

<asp:ScriptManager ID="ScriptManager1" runat="server">
    <Scripts>
        <asp:ScriptReference Path="~/HoverButton.js" />
    </Scripts>
</asp:ScriptManager>

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

<asp:Button ID="Button1" runat="server" Text="Простая кнопка" />
<asp:Label ID="Label1" runat="server" />

И, наконец, необходимо создать клиентский элемент управления и присоединить обработчики событий. Для создания элемента управления используется псевдоним $create из ASP.NET AJAX (который инициирует запуск метода Sys.UI.Component.create()) при первой загрузке страницы. На этом этапе предоставляется полностью квалифицированное имя класса элемента управления, другие свойства, которые требуется установить (такие как text, style и обработчики событий), и ссылку на базовый объект в странице (который можно получить через псевдоним $get).

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

function pageLoad(sender, args) {
        $create(CustomControls.HoverButton, {
            text: 'HoverButton Control',
            element: { style: { fontWeight: "bold", borderWidth: "2px" } }
        }, { click: start, hover: doSomethingOnHover, unhover: doSomethingOnUnHover },
        null, $get('Button1'));
}

Как только компонент зарегистрирован вызовом $create, ссылку на него можно получить в любой точке далее, используя $find. Обработчики событий имеют следующий вид:

function doSomethingOnHover(sender, args) {
    hoverMessage = "Вы навели мышь на кнопку"
    $get('Label1').innerHTML = hoverMessage;
    $find('Button1').set_text(hoverMessage);
}

function doSomethingOnUnHover(sender, args) {
    $get('Label1').innerHTML = "";
}

function start(sender, args) {
    alert("Запуск обработчика щелчка по кнопке");
}

Важно понять различие между $find и $get. Псевдоним $get извлекает HTML-элемент из страницы (такой как элемент <button>). Псевдоним $find получает полный клиентский компонент ASP.NET AJAX (подобный объекту HoverButton). Ясно, что если требуется взаимодействовать со свойствами, определенными в специальном элементе управления, нужно применять $find, чтобы получить объект элемента управления.

Клиентский компонент ASP.NET AJAX

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

Специальные элементы управления - не единственный тип клиентских компонентов ASP.NET AJAX, который можно применять. Можно создавать также специальные компоненты, не имеющие визуального представления. (Например, веб-элемент управления Timer из ASP.NET AJAX использует клиентский компонент.) Или же можно применять поведения (классы, унаследованные от Behavior), расширяющие поведение существующих элементов страницы. Поведения используются расширителями элементов управления, которым посвящена следующая статья.

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