• Microsoft .NET
  • ASP.NET
  • Microsoft ASP.NET AJAX: получение данных с сервера при помощи веб-сервисов

Microsoft ASP.NET AJAX: получение данных с сервера при помощи веб-сервисов

ОГЛАВЛЕНИЕ

Для разработки интерактивных веб-приложений Microsoft ASP.NET AJAX Framework предоставляет две модели, основанные  на клиенте (клиентская) и на сервере (серверная).  С моделью, основанной на клиенте, разработчики используют стандартные элементы управления ASP.NET - GridView, Button, TextBox и т.д., но при этом располагают их в пределах элемента управления UpdatePanel. Элемент UpdatePanel автоматически преобразовывает постбэки в частичные страничные постбэки и незаметно обновляет внешний вид страницы изменениями, сделанными кодом серверной стороны. С другой стороны, с моделью, основанной на клиенте, разработчик ответственен за написание JavaScript, который выполняет асинхронные вызовы к серверу, также как и скрипт, который обновляет страницу по запросу.

Модель, основанная на сервере, легче в использовании и более знакома разработчикам, имеющим опыт работы с элементами управления ASP.NET, при этом не использующим   JavaScript и HTML. Тем не менее, эта легкость в использовании дается высокой ценой: серверная модель разработки подразумевает кочевание значительного объема данных между клиентом и сервером при каждом частичном постбэке. Вкратце, UpdatePanel посылает состояние внешнего вида страницы при частичном постбэке и получает данный вид (наверняка, модифицированный) обратно в качестве ответа, независимо от того, необходимо ли оно для выполнения логики серверной стороны. Более того, в качестве ответа возвращено полностью обработанное содержимое UpdatePanel, даже если была модифицирована совсем небольшая часть UpdatePanel.

Клиентская модель разработки веб-приложений подразумевает больше работы, чем в случае с моделью, основанной на сервере, а также знание HTML, JavaScript и Document Object Model (DOM). Но использование данной модели может значительно уменьшить количество информации, обмениваемой между клиентом и сервером во время частичного постбэка. В данной статье мы рассмотрим одну из техник получения данных серверной стороны в модели, основанной на клиенте. Читайте далее, чтобы узнать больше об этом!

Основы веб-сервисов

Веб-сервисы (Web Services) - это механизм, благодаря которому два компьютера могут взаимодействовать через сеть. Веб-сервисы чаще всего реализованы при помощи модели клиент/сервер, где сервер производит веб-сервис, который затем будет использован клиентом. К примеру, веб-сайт о погоде может предоставить веб-сервис, который возвращает текущие погодные условия определенного города. Клиентское приложение, будь это настольное приложение или другой веб-сайт, может вызвать данные веб-сервиса, передавая интересующий город. Данный сайт о погоде должен ответить, возвратив текущие погодные условия города, что затем можно отобразить клиентское приложение. Вкратце, веб-сервисы (Web Services) позволяют осуществить взаимодействие между компьютерами, используя стандартные протоколы транспортировки и кодировки.

Многие веб-сервисы посылают и принимают данные, используя SOAP, что является протоколом, основанным на XML, служащим для кодировки сообщений. К примеру, когда клиентское приложение вызывает веб-сервис погоды, оно должно сделать это, отослав сообщение, как это сделано в следующем коде (т.е. ссылка веб-сервиса, как www.weathersite.com/GetWeatherReport.asmx):
<soap:Envelope>
   <soap:Body>
      <GetWeatherReport>
         <CityName>San Diego</CityName>
      </GetWeatherReport>
   </soap:Body>
</soap:Envelope>
Веб-сервис должен ответить, послав текущие погодные условия для города San Diego, закодированные в сообщении SOAP:
<soap:Envelope>
   <soap:Body>
      <GetWeatherReportResponse>
         <Conditions>Cloudy</Conditions>
         <Temperature>56</Temperature>
      </GetWeatherReportResponse>
   </soap:Body>
</soap:Envelope>
Создание и использование веб-сервисов в ASP.NET очень легко, поскольку .NET Framework обрабатывает всю работу нижних уровней, такую, как создание, отсылка, принятие и анализ сообщений SOAP. Чтобы создать веб-сервис, вам всего лишь необходимо добавить новый файл веб-сервиса к проекту вашего веб-сайта. Это создаст класс, где вы можете указать, какие методы будут доступны при помощи веб-сервиса. Аналогично, при использовании веб-сервиса из приложения, вы должны всего лишь сослаться на веб-сервис, и Visual Studio автоматически создаст прокси-класс (proxy class), который будет иметь такой же интерфейс, как и веб-сервис. Чтобы выбрать веб-сервис в клиентском приложении, просто вызовите соответствующий метод в прокси-классе.


Вызов веб-сервиса из приложения ASP.NET AJAX

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

  1. Сообщения веб-сервисов обычно закодированы соответственно протоколу SOAP. Тем не менее, AJAX-приложения предпочитают JSON - альтернативный стандарт кодировки, более подходящий для JavaScript.
  2. При использовании веб-сервиса из клиентского приложения, Visual Studio создает прокси-класс (proxy class) на клиенте, который дублирует интерфейс веб-сервиса. Тем не менее, при вызове веб-сервиса из JavaScript клиентской стороны нам нужно иметь прокси-класс, основанный на JavaScript.

Хорошей новостью является то, что Microsoft позволяет решить данные проблемы таким образом, что разработчику необходимо выполнить всего лишь  несколько действий. Что касается первого препятствия, Microsoft добавила классы атрибутов к .NET Framework , которые вы можете использовать для модификации вашего класса веб-сервиса таким образом, чтобы он указывал то, что веб-сервис должен также поддерживать сообщения, закодированные в формате JSON. Вкратце, вы можете указать .NET Framework разрешать клиентам вызывать ваш веб-сервис, используя сообщения JSON в дополнение к стандартным SOAP-сообщениям, добавив всего одну строку кода.

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

Создание веб-сервиса, который может быть вызван из приложения ASP.NET AJAX

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

Начните с добавления нового веб-сервиса к вашему веб-сайту ASP.NET щелкнув правой кнопкой мыши по каталогу, где вы хотите разместить финальную точку сервиса, и затем выбрав опцию добавления нового элемента (Add New Item) и шаблон веб-сервиса (Web Service template). Назовите новый веб-сервис NorthwindServices.asmx. Код данного веб-сервиса расположен в отдельном файле, названном NorthwindServices.asmx.vb или NorthwindServices.asmx.cs и является местом, где мы укажем методы для нашего веб-сервиса. Создайте метод Add вписав следующий код:

Imports System.Web
Imports System.Web.Services
Imports System.Web.Services.Protocols

<WebService(Namespace:="http://tempuri.org/")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Public Class NorthwindServices
   Inherits System.Web.Services.WebService

   <WebMethod()> _
   Public Function Add(ByVal x As Integer, ByVal y As Integer) As Integer
      Return x + y
   End Function
End Class

Для того, чтобы данный метод был доступен приложению ASP.NET AJAX , нам необходимо отметить данный сервис как "script service" (скриптовый сервис). Для этого нам нужно модифицировать класс веб-сервиса с атрибутом System.Web.Script.Services.ScriptService. Просто добавьте атрибут до определения класса веб-сервиса следующим образом:

<WebService(Namespace:="http://tempuri.org/")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
<System.Web.Script.Services.ScriptService()> _
Public Class NorthwindServices
   ...
End Class

Имея данный дополнительный атрибут, веб-сервис NorthwindServices может теперь принимать сообщения JSON в дополнение к стандартным сообщениям SOAP.


Вызов веб-сервиса из приложения ASP.NET AJAX

Для того, чтобы вызвать веб-сервис из приложения ASP.NET AJAX, вам сначала необходимо оповестить элемент управления ScriptManager о том, что для вашего сервиса необходим прокси-класс, основанный на JavaScript. Это выполняется путем указания пути к сервису в секции <Services> элемента ScriptManager:

<asp:ScriptManager ID="ScriptManager1" runat="server">
   <Services>
      <asp:ServiceReference Path="pathToWebService" />
   </Services>
</asp:ScriptManager>

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

Пример приложения, доступный в конце данной статьи, включает в себя страницу, которая использует метод Add веб-сервиса. Страница содержит два элемента TextBox , где пользователь может ввести суммируемые числа. Также, на странице есть кнопка, при нажатии которой будет вызван веб-сервис из JavaScript и будет отображена сумма в элементе <span> , названном sum. Разметка для пользовательского интерфейса будет выглядеть следующим образом:

<asp:TextBox runat="server" ID="Num1" Columns="3"></asp:TextBox>
+
<asp:TextBox runat="server" ID="Num2" Columns="3"></asp:TextBox>
=
<span id="sum"></span>
<br />
<input type="button" id="AddNumbers" value="Вычислить сумму" onclick="ComputeSum()" />

Обратите внимание на то, что кнопка "Вычислить сумму " реализована в качестве <input type="button" /> , вместо простого элемента Button, потому, что когда кнопка будет нажата, нам нужно запустить код JavaScript (вместо выполнения постбэка). Обработчик события onclick кнопки с клиентской стороны ссылается на функцию ComputeSum, написанную в JavaScript. Функция ComputeSum вызывает метод Add веб-сервиса, передавая в него значения, введенные пользователем в элементы управления Num1 и Num2 (TextBox).

function ComputeSum() {
   var n1 = document.getElementById('<%=Num1.ClientID %>').value;
   var n2 = document.getElementById('<%=Num2.ClientID %>').value;

   NorthwindServices.Add(n1, n2, OnComputeSumSuccess, OnComputeSumError);
}

Функция document.getElementById(id) используется для получения ассоциированного HTML-элемента из документа. Поскольку элементы управления - Num1 и Num2 - являются элементами управления ASP.NET , то будет благоразумным ссылаться на значения свойств ClientID элемента, вместо того, чтобы ссылаться на литеральные значения ID, Num1 и Num2. Причиной этого является то, что значения ID элемента могут быть модифицированы во время выполнения в случае, если TextBox появится в пределах именного контейнера (к примеру, мастер-страницы (Master Page)).

Чтобы вызвать метод Add вам нужно передать четыре параметра: два целочисленных значения для суммы, JavaScript-функцию, которую нужно выполнить в случае, если вызов будет завершен успехом (OnComputeSumSuccess) , а также JavaScript-функцию на случай, если вызов завершится ошибкой (OnComputeSumError). Вот код для двух упомянутых функций:

function OnComputeSumSuccess(results) {
   var sumLabel = document.getElementById('sum');
   sumLabel.innerHTML = results;
}

function OnComputeSumError(results) {
   alert('Произошла ошибка при вызове сервиса Add : ' + results.get_message());
}

Функция OnComputeSumSuccess ссылается на sum span-элемент и назначает его свойство innerHTML возвращенному результату. Функция OnComputeSumError отображает сообщение об ошибке в модальном диалоговом окне.

Следующий рисунок демонстрирует данную функциональность в действии. При первом посещении страницы пользовательский интерфейс включает в себя два текстовых поля и кнопку "Compute Sum" (Вычислить сумму).

 

После введения некоторых значений, к примеру 4 и 7, и нажатия кнопки "Compute Sum", функция ComputeSum клиентской стороны будет запущена и пошлет HTTP-ответ веб-сервису NorthwindServices.asmx со следующим содержимым:

{"x":"4","y":"7"}

Указанный выше JSON будет получен веб-сервисом и преобразован в значения входных параметров (4 и 7), затем метод Add будет вызван с этими значениями. Сумма будет подсчитана (11) и результаты будут возвращены обратно к клиенту:

{"d":11}

Получив ответ, метод OnComputeSumSuccess будет вызван, передавая 11 в качестве возвращаемого значения. Данное значение будет отображено в качестве содержимого элемента <span>.

 


Сравнение количества переданного трафика в обоих случаях

Модель разработки приложения, основанная на клиенте, требует немного больше усилий для реализации, чем в случае с серверной моделью. Чтобы реализовать функциональность добавления (Add) в случае с серверной моделью необходимо преобразовать кнопку "Compute Sum" и элемент <span> в элементы управления Button и Label соответственно, расположить их в UpdatePanel и создать обработчик события Click для кнопки, где мы произведем суммирование двух чисел и отобразим их результат в элементе Label. Но преимуществом модели, основанной на клиенте, будет то, что у вас будет больше контроля над информацией, обмениваемой между клиентом и сервером. Более того, количество информации в данном случае будет значительно меньше.

Чтобы продемонстрировать весь выигрыш в объеме информации, я создал две страницы с одинаковой функциональностью, но одна использует модель, основаную на клиенте, а другая использует серверную. В частности, страница отображает товары из базы данных Northwind в элементе GridView и включает в себя интерфейс "Quick Summary Report" (Быстрый отчет) , который позволяет посетителю запускать один из трех запросов:

  1. "How many products cost less than X dollars?" - сколько товаров стоят меньше Х долларов?
  2.  "How many products cost more than X dollars?" - сколько товаров стоят больше Х долларов?
  3. "How many products cost exactly than X dollars?" - сколько товаров стоят Х долларов?

Операция выбирается в выпадающем списке, а значение X введено при помощи текстового поля (смотрите следующий рисунок).

 

Страница, которая реализует подход, основанный на сервере, использует два элемента UpdatePanels: один для секции "Quick Summary Report" и второй для элемента GridView. Более того, у обоих свойства UpdateMode установлены в Conditional, тем самым обеспечивая то, что когда один UpdatePanel вызывает частичный постбэк, другой UpdatePanel не будет вовлечен в процесс. Тем не менее будет отправлена вся информация внешнего вида, независимо от того, который UpdatePanel вызывает частичный постбэк, и это состояние внешнего вида содержит в себе информацию об элементах управления обоих UpdatePanels.

Нажатие на кнопку "Reveal Answer" на странице, которая реализует серверную модель разработки, вызывает частичный постбэк и выполняет обработчик события Click элемента Button. Данный обработчик события определяет число товаров, которые удовлетворяют указанному значению, затем обновляет элемент Label результатами. Но ведь ясно, что единственная информация, которая должна быть передана между клиентом и сервером, это выбранная операция, количество долларов и результирующее число товаров, которые удовлетворяют запрос. Тем не менее, серверная модель разработки передает гораздо больше информации. Во время частичного постбэка на сервер будет отправлено около 2,400 бит информации. (Заметка: вы можете использовать Fiddler в качестве инструмента для исследования информации, отосланной во время частичного постбэка. Данный программный продукт был рассмотрен в статье "Поиск неисправностей веб-сайта путем изучения HTTP-трафика ".)

ScriptManager1=QuickSummaryUpdatePanel%7CRevealAnswer&__EVENTTARGET=&__EVENTARGUMENT=
&__VIEWSTATE=%2FwEPDwULL...&Operation=CostMore&QueryCost=5.00
&AllProducts%24ctl02%24ctl00=on&AllProducts%24ctl11%24ctl00=on
&__ASYNCPOST=true&RevealAnswer=Reveal%20Answer!

Большая часть данных получена от состояния внешного вида (который я вырезал в указанном выше коде). При этом сервер отвечает 3,500 битами информации, которая включает в себя полностью разметку области в UpdatePanel (даже притом, что было изменено только свойство Text элемента Label) наряду со всем состоянием внешнего вида.

930|updatePanel|QuickSummaryUpdatePanel|
<p>
   <b>Quick Summary Report:</b>
   <br />
   How many products
   <select name="Operation" id="Operation">
<option selected="selected" value="CostMore">Cost More Than</option>
<option value="CostLess">Cost Less Than</option>
<option value="CostExact">Cost Exactly</option>

</select>
          
   $<input name="QueryCost" type="text" value="5.00" size="8" id="QueryCost" style="text-align: right;" />?
</p>
<p>
   <input type="submit" name="RevealAnswer" value="Reveal Answer!" id="RevealAnswer" />
      
   <span id="QueryAnswer" style="background-color:Yellow;font-size:Large;font-weight:bold;">The answer to your query: 75 products!</span>
</p>
|0|hiddenField|__EVENTTARGET||0|hiddenField|__EVENTARGUMENT||1904|hiddenField|__VIEWSTATE|/wePDwU...


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

Пример приложения, доступный в конце данной статьи, включает в себя реализацию обеих моделей. Реализация модели, основанной на клиенте, использует вызовы веб-сервиса для определения числа товаров, удовлетворяющих указанному критерию, тем самым значительно уменьшает общий трафик. Во-первых, от клиента к серверу посылается только необходимая информация, а именно, операция на выполнение и значение цены, веденое в текстовое поле. Поиск товаров, которые стоят больше $5.00 в результате породит запрос, который потребляет всего 43 бита (вместо более 2,400, как упоминалось ранее для модели, основанной на сервере).

{"operation":"CostMore","queryCost":"5.00"}

А ответ сократился с 3,500 битов до 8!

{"d":75}

В таком случае, использование модели, основанной на клиенте, заставит нас написать немного больше кода HTML и JavaScript , но при этом мы получаем больше возможностей управления взаимодействием между клиентом и сервером, а также возможность уменьшения объема обмениваемой информации. И в результате мы получим более быстрое приложение.

Веселого программирования!

Scott Mitchell

Скачать исходные коды примера