Тестирование Web-сервиса
Для тестирования ASMX Web-сервиса нужно вызвать его из браузера, и все. Скопируйте Calc.asmx в wwwroot и введите в строке адреса своего браузера:
http://localhost/calc.asmx
Результат на рис. 2. ASP.NET в ответ на HTTP-запрос к Calc.asmx сгенерировала HTML-страницу с описанием Web-сервиса. Имя и описание из атрибута WebService ASMX-файла отображены наверху страницы. Ниже расположен список Web-методов, реализованных сервисом, снабженный описаниями, заданными в атрибутах WebMethods.
Рис. 2. Calc.asmx в Internet Explorer
Щелкните «Add» в верхней части страницы, и ASP.NET отобразит другую страницу, которую можно использовать для тестирования метода Add (рис. 3). ASP.NET известны имя и сигнатура метода, которые имеются в метаданных DLL, получаемой в результате компиляции Calc.asmx.
Рис. 3. Тестовая страница для метода Add
ASP.NET даже генерирует HTML-форму, с помощью которой вы можете вызвать метод Add с заданными вами параметрами. Введите 2 и 2 в полях «а» и «b» и щелкните Invoke. XML, возвращенный Web-методом, появится в отдельном окне браузера (рис. 4).
Рис. 4. XML, возвращаемый методом Add
Формы, автоматически генерируемые ASP.NET по ASMX-файлам, позволяют тестировать разрабатываемый Web-сервис без написания для этих целей специальных клиентов. С их помощью вы также можете исследовать Web-сервис, написанный с помощью.NET Framework, просто указав его адрес в своем браузере. Попробуйте ввести в своем браузере адрес:
http://terraservice.net/terraservice.asmx
Это URL Microsoft TerraService — превосходного Web-сервиса, реализующего программный интерфейс к большой базе географических данных, известной как Microsoft TerraServer. На данный момент не обращайте внимания на детали — ниже мы используем TerraService при создании клиента Web-сервиса. Но заметьте, как много можно узнать о TerraService, просто просматривая страницы, сгенерированные для него ASP.NET.
Web-сервисы и фоновый код
Используя фоновый код, классы Web-сервиса можно вынести из ASMX-файлов в отдельно компилируемые DLL. На рис.5 показан Calc.asmx после его изменения, чтобы задействовать преимущества фонового кода. Теперь в этом файле только один оператор. Класс, указанный в этом операторе, реализован в Calc.cs. Следующая команда компилирует Calc.cs в Calc.dll:
csc /t:library calc.cs
После компиляции DLL нужно поместить в подкаталог bin корневого каталога приложения (например, wwwrooot\bin).
Calc.asmx
<%@ WebService Class="CalcService" %>
Calc.cs
using System;
using System.Web.Services;
[WebService (Name="Calculator Web Service",
Description="Performs simple math over the Web")]
class CalcService
{
[WebMethod (Description="Computes the sum of two integers")]
public int Add (int a, int b)
{
return a + b;
}
[WebMethod
(Description="Computes the difference between two integers")]
public int Subtract (int a, int b)
{
return a - b;
}
}
Рис. 5. Web-сервис Calc с использованием фонового кода
Применение фонового кода для Web-сервисов имеет те же преимущества, что и для Web-страниц: ошибки компиляции выявляются до развертывания сервиса, и появляется возможность использовать языки, не поддерживаемые ASP.NET непосредственно.
Базовый класс WebService
Очень часто в примерах ASMX-кода классы Web-сервиса являются производными от класса WebService:
class CalcService: WebService
WebService относится к пространству имен System.Web.Services. Производные классы наследуют от него свойства Application, Session, Context, Server и User, что позволяет Web-сервисам получить доступ к одноименным объектам ASP.NET Если ваш Web-сервис не использует эти объекты, например, если вы не применяете состояние приложения и состояние сеанса, то вы не обязаны наследовать от WebService.
Атрибут WebMethod
Атрибут WebMethod объявляет данный метод Web-методом. Если такой метод реализован Web-сервисом, то.NET Framework автоматически публикует его как Web-метод. Однако WebMethod, помимо сообщения инфраструктуре о том, какие методы являются Web-методами, а какие нет, предоставляет и другие возможности. Он поддерживает следующие параметры:
Имя параметра Описание
BufferResponse Включает/отключает буферизацию откликов.
CacheDuration Кэширует отклики данного метода на указанное число секунд.
Description Добавляет текстовое описание Web-метода.
EnableSession Включает/отключает поддержку состояния сеанса для данного Web-метода.
MessageName Задает имя Web-метода.
Transaction-Option Задает параметры обработки транзакций для Web-метода.
CacheDuration — это ASMX-эквивалент директивы @ OutputCache в файлах ASPX или ASCX: он позволяет кэшировать результаты работы метода, чтобы последующие вызовы исполнялись быстрее. Допустим, вы написали Web-метод, который возвращает текущее время:
[WebMethod]public string GetCurrentTime (){ return DateTime.Now.ToShortTimeString ();}Так как ToShortTimeString возвращает строку, содержащую минуты, а не секунды, нет смысла вызывать его слишком часто. Следующее объявление метода использует CacheDuration для кэширования результатов работы метода в течение следующих 10 секунд:
WebMethod (CacheDuration="10")]public string GetCurrentTime (){ return DateTime.Now.ToShortTimeString ();}Теперь результаты вызова метода могут быть на несколько секунд устаревшими, зато если Web-сервис получает очень много вызовов GetCurrentTime, то нагрузка на него пропорционально снизится.
Web-сервисы могут использовать те же средства поддержки состояния сеанса, что и обычные приложения ASP.NET Но по умолчанию поддержка состояния сеанса для Web-методов отключена. Вы можете включить ее — параметр EnableSession атрибута WebMethod. Если вы хотите применить в Web-сервисе состояние сеанса, задействуйте WebService как базовый класс (чтобы унаследовать свойство Session) и укажите EnableSession=«true«для каждого метода, использующего состояние сеанса:
class CalcService: WebService{ [WebMethod (EnableSession="true", Description="Adds an item to a shopping cart")] public void AddToCart (Item item) { ShoppingCart cart = (ShoppingCart) Session["MyShoppingCart"]; cart.Add (item); }}Состояние сеанса в Web-сервисах используется не так часто, как в обычных Web-приложениях, но его все же можно задействовать.
Параметр MessageName позволяет присвоить Web-методу имя, отличное от имени метода, его реализующего. Так, вы встроили в Web-сервис два метода Add — один для сложения целых, а другой для сложения вещественных чисел — и оба пометили как Web-методы:
[WebMethod]public int Add (int a, int b){ return a + b;} [WebMethod]public float Add (float a, float b){ return a + b;}Единственная проблема здесь в том, что этот код не компилируется. Web-методы в отличие от методов С# не могут перегружаться. Решение? Или изменить названия методов, или добавить в атрибуты WebMethod параметры MessageName:
WebMethod (MessageName="AddInts")]public int Add (int a, int b){ return a + b;} [WebMethod (MessageName="AddFloats")]public float Add (float a, float b){ return a + b;}Теперь методы С# по-прежнему перегружены, но соответствующие им Web-методы называются Addlnts и AddFloats.
Язык описания Web-служб
Чтобы другие разработчики могли потреблять написанный вами Web-сервис (т. е. писать для него клиенты), им нужно знать, кроме прочего, какие методы он пре доставляет, какие протоколы поддерживает, сигнатуры методов и адрес Web-cepвиса (URL). Вся эта и другая информация может быть описана на языке WSDL (Web Services Description Language).
WSDL — относительно молодой стандарт. Это XML-язык, предложенный IBM, Microsoft и др. (описание его синтаксиса см. по адресу http://www.w3.org/TR/wsdl). Описывать его детали не целесообразно. Во-первых, они уже описаны в указанной спецификации. Во-вторых, WSDL — это язык для компьютеров, а не для людей. В-третьих, генерация WSDL для Web-сервиса, созданного с помощью.NET Framework, тривиальна: просто задайте своему браузеру URL Web-сервиса и добавьте строку запроса WSDL:
http://www.wintellect.com/calc.asmx?wsdl
Просмотрите результаты (рис. 6) — здесь вы найдете элемент service, описы вающий Web-сервис, элементы operation, описывающие «операции» или Web-методы, которые поддерживает сервис, элементы binding, описывающие поддерживаемые Web-сервисом протоколы и другую информацию.
Рис. 6.WSDL-контракт для Calc.asmx
Публикуя свой Web-сервис, вы должны опубликовать и описывающий его WSDL-контракт. Для Web-сервиса на основе.NET Framework таким контрактом является просто URL с?wsdl на конце. Другие разработчики могут использовать этот контракт при написании клиентов для вашего Web-сервиса. Обычно им не нужно читать контракт самим. Вместо этого они передают его инструменту, генерирующему класс-оболочку, который содержит все нужные элементы для вызова Web-сервиса. В.NET Framework SDK тоже есть такое средство – утилита Wsdl.exe
Web-сервисы и сложные типы данных
Нетрудно понять, как передать Web-методам и получить от них данные простых типов. В конце концов, целые и другие примитивные типы в той или иной форме определены на любой платформе. Но как быть со сложными типами? Что, если вы определили свой класс или структуру и хотите использовать его как входной параметр или возвращаемое значение Web-метода? Поддерживаются ли сложные типы и, если да, как объявить их, чтобы они стали частью данного Web-сервиса?
Сложные типы поддерживаются и прекрасно работают, так как XML позволяет представить практически любой тип. В качестве примера рассмотрим Web-сервис на рис. 7. Он публикует метод FindStores, параметром которого выступает сокращенное называние штата (например, «СА»). FindStores вызывает локальный метод FindByState, обращающийся к БД Pubs, поставляемой с Microsoft SQL Server, для поиска всех книжных магазинов в указанном штате и возвращающий результат в виде массива объектов Bookstore. (Заметьте: FindByState не является Web-методом, так как у него нет атрибута WebMethod) FindStores возвращает массив клиенту. Bookstore — это специализированный тип, определенный в ASMX-файле.
На рис. 8 показан XML, возвращаемый FindStore при вызове его с параметром «СА». Массив объектов Bookstore сериализован в формате XML Сериализацию выполняет класс SysternXmlSerializationXmlSerializer. NET Framework, известный также как «XML-сериализатор». Клиентское приложение, получающее данный XML и обладающее описанием схемы, которая описывает его структуру и содержимое, может снова воссоздать объекты Bookstore. Или же оно может работать напрямую с XML так, как считает нужным.
Откуда клиент возьмет XML-схему, описывающую тип данных Bookstore? Конечно же, из WSDL-контракта сервиса. Посмотрите повнимательнее на WSDL-контракт для Locator.asmx и увидите, что тип данных Bookstore (и массив элементов типа Bookstore), определен в элементе types так:
<s:complexType name="ArrayOfBookstore"> <s:sequence> <s:element minOccurs="0" maxOccurs="unbounded" name="Bookstore" nillable="true" type="s0:Bookstore" /> </s:sequence></s:complexType><s:complexType name="Bookstore"> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="Name" nillable="true" type="s:string" /> <s:element minOccurs="1" maxOccurs="1" name="Address" nillable="true" type="s:string" /> <s:element minOccurs="1" maxOccurs="1" name="City" nillable="true" type="s:string" /> <s:element minOccurs="1" maxOccurs="1" name="State" nillable="true" type="s:string" /> </s:sequence></s:complexType>
Locator.asmx
<%@ WebService Language="C#" Class="LocatorService" %>
using System;
using System.Web.Services;
using System.Data;
using System.Data.SqlClient;
[WebService (Name="Bookstore Locator Service",
Description="Retrieves bookstore information from the Pubs database")]
class LocatorService
{
[WebMethod (Description="Finds bookstores in a specified state")]
public Bookstore[] FindStores (string state)
{
return FindByState (state);
}
Bookstore[] FindByState (string state)
{
SqlDataAdapter adapter = new SqlDataAdapter
("select * from stores where state = \'" + state + "\'",
"server=localhost;database=pubs;uid=sa;pwd=");
DataSet ds = new DataSet ();
adapter.Fill (ds);
DataTable table = ds.Tables[0];
Bookstore[] stores = new Bookstore[table.Rows.Count];
for (int i=0; i<table.Rows.Count; i++) {
stores[i] = new Bookstore (
table.Rows[i]["stor_name"].ToString ().TrimEnd
(new char[] { ' ' }),
table.Rows[i]["stor_address"].ToString ().TrimEnd
(new char[] { ' ' }),
table.Rows[i]["city"].ToString ().TrimEnd
(new char[] { ' ' }),
table.Rows[i]["state"].ToString ().TrimEnd
(new char[] { ' ' })
);
}
return stores;
}
}
public class Bookstore
{
public string Name;
public string Address;
public string City;
public string State;
public Bookstore () {}
public Bookstore (string name, string address, string city,
string state)
{
Name = name;
Address = address;
City = city;
State = state;
}
}
Рис. 7. Web-сервис поиска книжных магазинов
Имея это описание, клиент может определить собственный класс Bookstore и инициализировать массив элементов Bookstore путем их десериализации. Это не так сложно, как кажется. Если клиент написан с помощью.NET Framework, определение класса генерируется посредством инструментов, а десериализацию инфраструктура выполняет автоматически.
Рис.8. XML, сгенерированный методом FindStores
Locator.asmx демонстрирует, что писать Web-методы, использующие специализированные типы, нетрудно. Однако следует иметь в виду два обстоятельства.
■ Так как строки запросов могут содержать только простые пары «имя — значение», вы не можете передавать сложные типы Web-методу с помощью HTTP GET и POST. Это не ограничение, если для вызова Web-методов применяется SOAP, но не позволит ASP.NET сгенерировать тестовые страницы для методов, принимающих сложные типы. Если на тестовой странице вы видите сообщение «No test form is available because this method does not support HTTP GET»1 или похожее на него, то вы наткнулись на метод, входные параметры которого не могут быть представлены в строке запроса. Формы тестирования ASP.NET вызывают методы командами HTTP GET.
■ Поля или свойства классов или структур, передаваемых Web-методу или возвращаемых им, которые предполагается сериализовать при сериализации этих классов или структур, должны быть открытыми. Это требование объясняется тем, что XML-сериализатор.NET Framework игнорирует неоткрытые члены.
Помните об этом, и у вас не будет проблем при использовании Web-методов со специализированными типами данных.