Search
Write a publication
Pull to refresh

Создание ASP.NET AJAX контрола с возможностью обмена данными с сервером

Reading time4 min
Views4.7K
Здравствуй, Хабр!

Хочу поделиться своим опытом решения проблемы, ответа на которую я трудно найти в Интернете (русскоязычном — уж точно).

Столкнулся я недавно с тем, что мне было поручено написать ASP.NET AJAX контрол серверной стороны. Это такой контрол, который доступен при редактировании страницы ASP.NET в дизайнере (toolbox), часть логики его находится на сервере, а часть на клиенте, взаимодействие происходит аяксово. Суть работы моего контрола — простой ComboBox, который получал бы данные динамически с сервера (предполагается, что данных так много, что загрузить их все разом нельзя), а также фильтровал бы список на основании введенных в текстовое поле символов. Это почти реализовано в AJAX Control Toolkit, только список там грузится целиком.
В интернете довольно много различных статей и блогов по теме создания своих контролов, однако, как ни странно, все они ограничиваются простенькими примерами, в которых напрочь отсутствует описание механизма взаимодействия клиента и сервера. Большинство источников приводит цитаты из стандартных микрософтовских учебников, где аякс заключается в том, что при наведении мышки на кнопку та меняет свою картинку (дет. сад). Мне же надо было, чтобы в список на клиенте подтягивались данные из списка, который хранился на сервере (это может быть просто список, какой-либо ORM и вообще все, что бывает IEnumerable).

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

Перейду к сути. Для того, чтобы клиент мог асинхронно запрашивать данные с сервера, в ASP.NET существует несколько способов. Наиболее удобный из них — взаимодействие посредством web-сервиса. Такой подход позволяет передавать данные туда-обратно в любом удобном для Вас виде, начиная от строки, заканчивая полноценными объектами (в том числе составными). Всю сериализацию за вас делает ASP.NET. Однако, при всей своей привлекательности, такой подход не мог быть применим к моей задаче, поскольку требовался контрол со всей функциональностью «out of the box». То есть добавил его на страницу, указал источник данных, и все — остальное он сделает за тебя. Web-сервис же не может быть включен в сборку компонента, а соответственно, и на последующие страницы, его использующие. Для этого программистам-пользователям моего контрола необходимо было бы дописывать лишний сервис, без которого вполне могли бы и обойтись.

Вторым методом является реализация интерфейса ICallbackEventHandler. Данный интерфейс служит для того, чтобы указать, что реализующий его контрол может быть целью события обратного вызова клиентской стороны (target of a callback event — MSDN). Этот интерфейс предоставляет два метода: RaiseCallbackEvent, который принимает данные события от браузера в строковом параметре, и GetCallbackResult, который занимается возвращением результата обратно.
Такой подход явился для меня наиболее приемлемым, так как функциональность ajax будет всегда при контроле, где бы он ни оказался. Приведу же теперь код серверной и клиентской сторон, чтобы прояснить ситуацию.

Сервераня часть:
  1. #region ICallbackEventHandler implementation
  2.  
  3. public void RaiseCallbackEvent(string eventArg)
  4. {
  5.   m_callbackEventArg = eventArg;
  6. }
  7.  
  8. public string GetCallbackResult()
  9. {
  10.   string result = "";
  11.  
  12.   if(DataSource != null)
  13.   foreach (object o in DataSource)
  14.   {
  15.     string item = o.ToString();
  16.  
  17.     if (item.StartsWith(m_callbackEventArg))
  18.       result += item + "||";
  19.   }
  20.  
  21.   if (result.Length > 2)
  22.     return result.Substring(0, result.Length - 2);
  23.   else
  24.     return "";
  25. }
  26.  
  27. #endregion

В методе RaiseCallbackEvent всего лишь запоминается строка, пришедная от клиента. В ней хранится текст, который пользователь ввел на сайте в поле ввода нашего контрола. По этому значению будут фильтроваться результаты выборки.
Метод GetCallbackResult наделен несколько большей логикой. Здесь идет поэлементный обход списка (хранится в DataSource), каждая строка в нем сравнивается со значением фильтра, и если все хорошо, заносится в результат. Да, стоит отметить, что недостатком такого подхода является то, что как запрос, так и результат должны быть строкой. То есть это наша забота, как сериализовать данные. В моем случае достаточно было разделять разные строки подстрокой ||, однако для более серьезных проектов придется несколько заморочиться.

Теперь же давайте взглянем на клиентский код:
  1. _populateListBegin: function() {
  2.   if (this.get_loadDynamically() == false) {
  3.     this._filterList(this._textFilter);
  4.     this._displayList();
  5.   }
  6.   else {
  7.     WebForm_DoCallback(this.get_element().id, this._textFilter, this._onPopulateListComplete, this.get_element().id, null, true);
  8.   }
  9. },
  10.  
  11. _onPopulateListComplete: function(result, context) {
  12.   var rscb = $find(context);
  13.   rscb._processCallbackResult(result);
  14. }

Этот код — часть прототипа объекта списка, реализованного на JavaScript на клиенте. Первая функция вызывается при нажатии пользователем на кнопку «развернуть» списка (регистрация события и код кнопки опущен, так как относится к другой теме). Интересным для нас моментом является вызов функции WebForm_DoCallback, которая и запускает процесс обращения к серверной стороне. В качестве параметров она принимает ID объекта, который вызвал callback-запрос, параметр запроса (строка), делегат на функцию-обработчик ответа, контекст, который может быть использован при обработке ответа, делегат на функцию-обработчик ошибки (здесь он нам не нужен ввиду простоты контрола), и булев параметр, означающий асинхронную или нет отправку. Собственно это и есть вся магия ajax запроса.
Функция _onPopulateListComplete будет вызвана тогда, когда будет получен ответ от сервера. Надо заметить, что она выполняется не в контексте объекта, поэтому обращение к this может дать неверный результат. Именно поэтому я передавал в контексте id объекта.

Вот все то немногое, что нужно сделать при реализации серверного контрола с полноценным AJAX обменом. Сильно не бейте, это мой первый пост. Если какие вопросы по содержанию (уверен, они будут) — отвечу в комментах с удовольствием.
Tags:
Hubs:
Total votes 16: ↑13 and ↓3+10
Comments2

Articles