logo
[ПСП] / lab22

Web-cлужбы

Во многих отношениях эта глава обьединяет в единое целое многое из того, что мы узнали из предыдущих глав этой книги. Речь пойдет о web-службах ASP.NET — модулях кода .NET (обычно установленных на сервере IIS), к которому возможно удаленное обращение по протоколу HTTP.

Как мы увидим, web-службы строятся на основе трех взаимосвязанных техно­логий: Web Service Description Language (WSDL, язык описания web-служб), про­токола подключения (HTTP-GET, HTTP-POST и SOAP) и службы обнаружения (discovery service). Как обычно, вначале мы поработаем с элементарным приме­ром, создав web-службу, которая выполняет роль калькулятора, а затем создадим более изощренный пример web-службы, связанной с миром автомобилей, которая будет возвращать объекты ADO.NET DataSet, массивы ArrayList и пользователь­ские типы.

После того как мы научимся создавать web-службы, мы обратимся к созданию прокси-классов (при помощи Visual Studio.NET и утилиты wsdl). Эти прокси-классы позволят обращаться к web-службе как клиентам Web, так и другим клиентам, в том числе с помощью консольных приложений и обычных приложений Windows Forms.

Роль web-служб

Если посмотреть на web-службу «с высоты птичьего полета», то это — всего лишь блок кода, к которому можно обратиться по протоколу HTTP. Однако сама по себе эта формулировка значит уже очень многое. Подавляющее большинство исполь­зуемых в настоящий момент технологий удаленной активации кода привязаны к конкретным протоколам (при этом требующих постоянных и надежных соедине­ний), платформам и языкам программирования. В DCOM для обращения к уда­ленным типам СОМ используется требующий высокоскоростных надежных со­единений RPC. В CORBA используется несколько протоколов, но все они также требуют постоянного подключения и надежных соединений. EJB (Enterprise Java Beans) требует использования определенного протокола плюс языка программи­рования Java.

.NET сильно отличается от всех этих технологий. Прежде всего, как мы много раз могли убедиться, .NET обеспечивает большую степень языковой независимос­ти, чем что-либо другое. Мы можем создавать при помощи С#, VB.NET или любо­го другого языка программирования для работы с .NET типы, к которым можно будет обращаться из клиента на любом .NET-совместимом языке. Кроме того, для обращения к web-службам ASP.NET нам нужно, чтобы на данной конкретной плат­форме был реализован протокол HTTP — и все! При всем существующем разно­образии платформ и операционных систем вряд ли найдется платформа, на кото­рой не был бы реализован HTTP.

Таким образом, как web-разработчик, вы обнаружите, что для создания web-службы ASP.NET вы сможете использовать привычный и любимый язык програм­мирования. Как клиента web-служб, думаю, вас обрадует тот факт, что для вызова методов типов web-служб вполне можно обойтись стандартным HTTP. Кроме того, как мы вскоре обнаружим, во взаимодействии с протоколом HTTP вы сможете также использовать стандартные XML и SOAP (Simple Object Access Protocol), что также немаловажно.

Web-служба строится из тех же типов, что и любая сборка .NET: из классов, интерфейсов, перечислений и структур, которые играют для клиента роль «черно­го ящика», отвечающего на запросы. Единственное важное ограничение, о кото­ром необходимо постоянно помнить, связано с тем, что web-службы предназначе­ны для обработки удаленных вызовов и поэтому в них следует избегать применения типов для работы с графическим интерфейсом. Web-службы предназначены для другого: они должны уметь выполнить какое-либо действие по запросу пользова­теля (произвести вычисления, считать данные из источника) и ждать следующего запроса.

Еще один важный момент, связанный с web-службами, который обязательно необходимо осознать, состоит в том, что для них вовсе не обязательно использо­вать клиентов, работающих через браузер. К web-службе вполне могут обращать­ся и обычные консольные или Windows-клиенты (локальные, клиенты терминаль­ных служб и т. п.). Для этого в .NET предусмотрены специальные средства, которые позволяют генерировать так называемые прокси-сборки. Мы обращаемся к типам этой прокси-сборки, как к обычному типу .NET, а она уже перенаправляет запрос в web-службу (при помощи HTTP или сообщений SOAP) и возвращает клиенту полученные результаты.

Инфраструктура web-службы

Web-службе (как и обычному приложению ASP.NET) обычно соответствует вир­туальный каталог на сервере IIS. Однако для web-службы вам потребуется также реализовать дополнительную поддерживающую инфраструктуру. К ней относятся:

В этой главе нам предстоит реализовать каждую часть инфраструктуры в на­ших примерах. А пока — краткое описание.

Протокол подключения

В web-службах ASP.NET (как и в ADO.NET) стандартный формат передачи ин­формации между службой и клиентом — это формат XML. Сама передача проис­ходит при помощи протокола HTTP. Мы можем использовать различные методы передачи информации — метод HTTP GET, HTTP POST и SOAP. Ориентировать­ся следует на SOAP, поскольку при помощи этого протокола мы можем обеспе­чить передачу очень сложных типов (пользовательских классов, объектов АDо. NET DataSet, массивов объектов и т. п.).

Служба описания

При обращении к удаленной web-службе клиент должен обладать полной инфор­мацией о членах типов web-службы, которые предоставлены в его распоряжение. Например, клиент должен иметь информацию о том, что он может вызвать метод Foo(), а также все необходимые параметры этого метода: какие параметры этот ме­тод принимает и что он возвращает. За предоставление клиенту этой информации и ответственна служба описания (description service) web-службы. Как обычно, сама информация о web-службе предоставляется в формате XML. XML schema, ко­торая используется для описания web-службы, называется WSDL (Web Service Description Language, WSDL).

Служба обнаружения

Служба обнаружения (discovery service) позволяют клиенту обнаруживать web-службы по адресу URL. Для этой службы используются файлы *.disco (от dis­covery), опять-таки в формате XML. Мы познакомимся с синтаксисом этих файлов в последней части этой главы.

Обзор пространств имен web-служб

Как вы уже, наверное, догадываетесь, разработчики .NET заготовили для нас мно­жество типов, которые могут быть использованы как для построения самих web-служб, так и для создания необходимой инфраструктуры. Эти типы определены в пространствах имен, представленных в табл.15.1.

Таблица15.1. Пространства имен для web-служб

Пространство имен System.Web.Services

В большинстве проектов по созданию web-служб единственные типы, с которыми нам придется взаимодействовать напрямую — это типы пространства имен Sys­tem.Web.Services. Этот набор типов не так уж велик: типы System.Web.Services пред­ставлены в табл.15.2.

Таблица15.2. Типы пространства имен System.Web.Service

Пример элементарной web-службы

Перед тем как приступать к подробностям, давайте сформируем мысленный образ того, о чем идет речь, и создадим очень простую web-службу. Для этого запустим Visual Studio.NET и создадим новый проект С# на основе шаблона Web Service (рис.15.1). Мы назовем этот проект CalcWebService.

Как для любого проекта ASP.NET, при создании проекта Web Service Visual Studio.NET автоматически создаст виртуальный каталог для этого проекта на сер­вере IIS (рис.15.2), а ненужные в этом каталоге файлы проекта *.sln и *.suo разме­стит в подкаталоге \MyDocuments\Visual Studio Projects.

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

Что же создала для нас Visual Studio? Набор файлов, сгенерированных автома­тически, представлен в окне Solution Explorer на рис.15.3.

Рис.15.1. Создаем новый проект Web Service

Рис.15.2. Для web-службы будет автоматически создан виртуальный каталогна сервере IIS

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

Файл Global.asax предназначен для организации реагирования на события гло­бального уровня (общие для всех сеансов подключения). Файл Web.config позво­ляет нам в формате XML определить основные параметры приложения ASP.NET (в данном случае нашей web-службы). Вся реальная работа у нас (как и в боль­шинстве реальных проектов) будет производиться исключительно с тремя файла­ми: *.asmx, *.asmx.cs и *.disco.

Рис.15.3. Исходный набор файлов проекта

Таблица 15.3. Наиболее важные файлы проекта Web Service

Исходный файл С# для web-службы (*.asmx.cs)

Как уже говорилось в предыдущих главах, одно из наиболее важных преимуществ ASP.NET— это возможность создавать web-приложения при помощи полнофунк­циональных объектно-ориентированных языков программирования (в нашем слу­чае С#), а не ограничиваться скриптами (как в классических ASP). Файл *.asmx можно представить себе как шаблон, на основе которого среда выполненияASP.NET генерирует код HTML, передаваемый в браузер клиента. Для файла *.asmx при помощи атрибута Codebehind («код за сценой») можно определить файл на «нор­мальном» языке программирования, в котором и будет реализована вся программ­ная логика web-службы. В нашем случае для web-службы был выбран проект С#, поэтому и файл Codebehind — *.asmx.cs, исходный файл С#.

Что же вложила в этот файл С# среда выполнения Visual Studio.NET? А вот что:

public class Servicel : System.Web.Services.WebService

{

public Service() { InitializeComponent(); }

private void InitializeComponent() {}

public override void Dispose() {}

};

Ничего интересного, за одним существенным исключением: для класса С# в качестве базового был выбран класс System.Web.Services.WebService. Сразу огово­римся, что это совершенно необязательно. Мы вполне можем создать web-службу, обойдясь и без этого базового класса. Определение будет тем же самым, только наш класс будет теперь производиться напрямую от System.Object, а замещенный метод Dispose() лучше закомментировать:

// А я все равно web-служба

public class Servicel

{

public ServiceO { InitializeComponent(); }

private void InitializeComponent() {}

// public override void Dispose() {}

};

Как мы сможем убедиться, функциональности нашей web-службы это нисколь­ко не повредит. Однако выбор в качестве базового класса WebServiсе автоматичес­ки обеспечивает нашей web-службе очень полезный набор членов, с которым мы вскоре познакомимся.

Реализуем методы web - службы

В нашей первой web-службе мы ничего не будем усложнять и ограничимся че­тырьмя методами, при помощи которых пользователь сможет производить эле­ментарные арифметические операции. Все эти методы будут доступны по HTTP, но чтобы среда выполнения ASP.NETпоняла, какие методы нужно выкладыватьпо HTTP для пользователей, эти методы нужно пометить атрибутом [WebMethod]. В общем, определение нашего класса Service мы сделаем таким:

public class Service1 : System.Web.Services.WebService

{

public Service() { InitializeComponent(); }

private void InitializeComponent() {}

public override void Dispose() {}

[WebMethod]

public int Add(int x, int y) { return x + y; }

[WebMethod]

public int Subtract(int x, int y) { return x - y; }

[WebMethod]

public int Multiply(int x, int y) { return x * y; }

[WebMethod]

public int Divide(int x , int y)

{

if(y = = 0)

{

throw new DivideByZeroException("Dude.can't divide by zero!");

}

return x / у;

}

}

Работа клиента с web-службой

После того как web-служба будет откомпилирована, запустим ее на выполнение (можно прямо в Visual Studio). По умолчанию в качестве клиента будет открыто окно нашего браузера, а в нем откроется страница HTML со списком всех методов, которые мы пометили атрибутом [WebMethod] — см. рис.15.4.

Рис.15.4. Клиент подключился к нашей web-службе

Конечно же, мы можем не только просматривать список методов web-службы, но и вызывать его прямо из браузера — заботливая среда выполнения ASP.NET позаботилась и об этом! Например, перейдем по гиперссылке на Add и введем в текстовые поля значения (рис.15.5). Осталось только нажать кнопку Invoke, и сре­да выполнения ASP.NETвызовет метод, передаст ему введенные значения и вер­нет нам результат в формате XML (рис.15.6).

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

http://Имя_хоста/Са1cWebServiсе/Са1cServiсе.asmx/Add?x=44&y=446

Как мы видим, запрос состоит из адреса страницы *.asmx с добавлением имени метода и парами имя — значение, представляющими параметры метода.

Как мы только что убедились, создать web-службу в ASP.NET— это очень про­сто. Нам еще предстоит поработать с примером более серьезной службы, однако пока мы обратимся к некоторым особенностям архитектуры web-служб ASP.NET.

Рис.15.5. Среда выполнения ASP.NETавтоматически сгенерирует текстовые поля для ввода параметров метода и прочий необходимый код

Рис.15.6. Результат вычислений, возвращаемый в формате XML

Тип WebMethodAttribute

Атрибут WebMethod (представленный типом WebMethodAttribute) должен обязатель­но быть указан для каждого метода web-службы, предоставляемого в распоряже­ние клиента. Как большинство других атрибутов .NET, для атрибута WebMethod мож­но использовать дополнительные параметры (которые на самом деле являются параметрами конструктора WebMethodAttribute). Например, мы можем предоставить клиенту дополнительную информацию о методе web-службы:

[WebMethod(Description = "Yet another way to add members!")]

public int Add(int x, int y){ return x + y; }

Как мы видим, параметр Description атрибута WebMethod очень похож на атрибут [helpstring] в IDL. А результатом его применения будет дополнительная инфор­мация о методе на web-странице (рис.15.7).

Рис.15.7. Параметр Description в действии

Если поинтересоваться, что же происходит при добавлении к методу парамет­ра Description, то окажется, что в коде WSDL (об этом — позже) файла *.asmx по­явился дополнительный тег <documentation>:

<operation name="Add">

<input message="so:AddSoapIn" />

<output message="so:AddSoapOut" />

<documentation>Yet another way to add numbers!</documentation>

</operation>

Параметр Description — не единственный, который можно использовать для атри­бута WebMethod. Наиболее важные параметры этого атрибута представлены в табл.15.4.

Таблица 15.4. Параметры атрибута WebMethod

Давайте рассмотрим на примере, для чего нужен параметр MessageName. Предпо­ложим, что в нашем web-калькуляторе появился дополнительный метод Add() для сложения двух значений с плавающей запятой:

[WebMethod(Description = "Add 2 integers,")]

public int Add(int x, int y) { return x + y; }

[WebMethod(Description = "Add 2 floats,")]

public float Add(float x, float y) { return x + y; }

Если мы попробуем откомпилировать наш проект, то никаких ошибок не бу­дет — с точки зрения С# все в порядке. Однако если мы попытаемся обратиться к web-службе из браузера, мы увидим не совсем то, на что рассчитывали:

System.Exception: Both Single Add(Single, Single) and Int32 Add(int32, Int32) use the message name 'Add'.

(System.Exception: И метод Single Add(Single, Single), и метод Int32 Add(int32, Int32) используют имя сообщения 'Add'.)

Конфликт имен произошел на уровне WSDL: необходимо, чтобы для каж­дого атрибута <soap:operation soapAction> (который используется для иденти­фикации метода web-службы) использовалось уникальное значение (то есть имя web-метода). Однако по умолчанию значением этого атрибута считается имя метода в определении этого метода в С#. Чтобы решить проблемы с конф­ликтом имен, достаточно для одного из методов добавить новое значение пара­метра MessageName:

[WebMethod(Description = "Add 2 integers,")]

public int Add(int x, int y) { return x + y; }

[WebMethod(Description = "Add 2 float,", MessageName = "AddFloats")] public float Add(float x, float y) { return x + y; }

После этого идентификаторы методов в WSDL станут разными:

<operation name="Add">

<soap:operation soapAction="http://tempuri.org/AddFloats" style="document" />

<input name="AddFloats">

<soap:body use="literal" />

</input>

<output name="AddFloats">

<soap:body use="literal" />

</output>

</operation>

<operation name="Add">

<soap:operation soapAction="http://tempuri.org/Add" style="document" />

<input>

<soap:body use="literal" />

</input>

<output>

<soap:body use="literal" />

</output>

</operation>

Если нам потребуется использовать описание для всей нашей web-службы, а не для отдельного метода, можно использовать для класса С# атрибут WebService с аналогичным параметром Description, например, так:

[WebService(Description = "The painfully simple web service" )]

public class Service1 : System.Web.Services.WebService

{

}

Если мы запустите нашу web-службу после этого добавления, результат будет таким, как показано на рис.15.8.

Рис. 15.8. Параметр Description атрибута WebService определяет описание для web-службы в целом

Базовый класс System.Web.Services.WebService

Как мы уже заметили, по умолчанию Visual Studio.NETпроизводит класс нашей web-службы от базового классаSystem.Web.Services.WebService. В принципе web-служба будет работать и без этого класса в качестве базового, однако в WebService предусмотрен набор очень полезных членов и вложенных типов, которые обеспе­чивают web-службе те же возможности, которые имеются у стандартного прило­жения ASP.NET. Обычно в программном коде приходится взаимодействовать со свойствами класса web-службы, унаследованными отWebServiсе. Наиболее важ­ные свойства WebService представлены в табл.15.5.

Таблица 15.5. Наиболее важные свойства класса WebService

Web Service Description Language (WSDL)

Во всех программных технологиях, использующих межъязыковое взаимодействие, используются специальные средства для описания программных модулей и типов в них. В СОМ для этого применяется язык IDL, в обычных приложениях .NET — метаданные сборки (манифест) и типов. Для web-служб ASP.NETтакое специ­альное средство также предусмотрено: это — WSDL.

WSDL (Web Service Description Language, язык описания web-служб) — это XML-совместимый язык, который полностью описывает для внешних клиентов возможности web-служб, методы, которые клиенты могут вызвать, а также под­держку протоколов подключения к web-службам (HTTP-GET, HTTP-POST и SOAP). Сразу скажем, что код WSDL в ASP.NETгенерируется автоматически. Например, в нашем примере WSDL-описание web-службы можно получить пря­мо из окна браузера. Если мы откроем в браузере страницу CalcService.asmx, то в верхней части страницы будет гиперссылка Service Description (рис.15.9). Перейдя по ней, можно прочитать код WSDL для нашей web-службы (рис.15.10).

Рис. 15.9. Ссылка на описание web-службы в формате WSDL

Поскольку код WSDL генерируется автоматически, мы имеем полное право и не думать о том, что там написано в коде WSDL для нашей web-службы. Доско­нально разбирать все возможности WSDL мы не будем, но все же обратим внима­ние на наиболее принципиальные моменты.

Прежде всего, любое определение web-службы на WSDL начинается с тега <definitions>. Далее следуют ссылки на узлы, определяющие протоколы подклю­чения к web-службе:

<?xml version="1.0" ?>

<definitions

xmlns:s="http://www.w3.org/2000/10/XMLSchema"

xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"

xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"

xmlns:urt="http://microsoft.com/urt/wsdl/text/"

xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"

xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"

xmlns:sO="http://tempuri.org/" targetNamespace="http://tempuri.org/"

xmlns="http://schemas.xmlsoap.org/wsdl/">

Рис.15.10. А это — само описание web-службы в формате WSDL

За ними следуют определения WSDL для каждого web-метода в терминах про­токолов HTTP-GET, HTTP-POST и SOAP (рис.15.11).

Для каждого метода предусмотрены отдельные определения In (для приема данных) и Out (для возврата данных клиенту), при этом каждая пара перечисляется отдельно для каждого протокола подключения. Например, вот пара In/Out в WSDL-определении метода Subtract для протокола подключения HTTP-POST:

<message name="SubtractHttpPostIn">

<part name="x" type"s:string" />

<part name="y" type'Vstring" />

</message>

<message name="SubtractHttpPostOut>

<part name="Body" element="sO:int" />

</message>

А вот так выглядит та же пара того же метода Subtract для протокола SOAP:

<message name="SubtractSoapIn">

<part name="parameters" element="sO:Subtract" />

</message>

<message name="SubtractSoapOut">

<part name="parameters" element="sO:SubtractResponse" />

</message>

Рис.15.11. Для каждого метода web-службы предусмотрены WSDL-определения для всех протоколов подключения

В WSDL, как и во многие другие метаязыки, глубоко вникать есть смысл в том случае, если вы собираетесь создать что-нибудь вроде своего собственного анали­затора кода WSDL или просмотрщика типов ASP.NET. Если создание таких про­граммных продуктов действительно входит в ваши планы, мы советуем вам также обратиться к типам пространства имен System.Web.Services .Description. Эти типы библиотеки базовых классов .NET позволяют удобно манипулировать кодом WSDL непосредственно из программы.

Протоколы подключения к web-службам

Как уже было сказано, основная задача любой web-службы ASP.NET— вернуть клиенту запрашиваемые им данные по протоколу HTTP. Однако для обмена дан­ными между клиентом и сервером можно использовать три разных метода (они и называются протоколами подключения — wire protocols): HTTP-GET, HTTP-POST и SOAP. Мы уже неоднократно упоминали об этих протоколах, однако для удобства сведения о них мы на этот раз сведем в таблицу:

Выбор протокола подключения определяет то, какими типами (передаваемы­ми методам web-служб и возвращаемыми ими клиентам) смогут обмениваться клиент и web-служба. Можно сделать лишь общее замечание о том, что наибольшие возможности обеспечивает протокол SOAP. Однако вначале мы рассмотрим при­менение протоколов HTTP-GET и HTTP-POST.

Таблица 15.6. Протоколы подключения к web-службам

Обмен данными при помощи HTTP-GET и HTTP-POST

При передаче данных методом HTTP-GET данные дописываются к строке запро­са в формате URL в виде пар имя — значение (сам адрес отделяется от набора зна­чений вопросительным знаком (?)). При применении HTTP-POST данные переда­ются при помощи тех же пар имя — значение, только они помещаются в специальное поле заголовка протокола HTTP. При использовании обычных методов возвра­щаемый клиенту результат всегда возвращается в простом формате XML в виде <имя_типа>3начение</имя_типа>.

HTTP-GET и HTTP-POST — методы очень простые и многим разработчикам хорошо знакомые. Однако их возможности оставляют желать лучшего: с их помо­щью мы не можем передавать сложные данные, такие как структуры или экземп­ляры объектов. Фактически клиент и web-служба могут обмениваться только ти­пами, представленными в табл. 15.7.

Таблица 15.7. Типы данных, которые можно передавать при помощи протоколов HTTP-GET и HTTP-POST

Передача данных при помощи HTTP-GET и HTTP-POST производится очень просто. Мы создаем web-страницу с формой HTML, а в качестве получателя для этой формы указываем файл *.asmx. Метод передачи данных определяется при по­мощи атрибута method тега <form>. Вот пример такой web-страницы (она будет на­зываться HTMLPage1.htm), которая будет принимать от пользователя два значения и передавать их методу Subtract() нашей web-службы CalcWebService:

<HTML>

<НЕАD>

<TITLE><?TITLE>

<МЕТА NAME="GENERATOR" Content=Microsoft Visual Studio 7.0">

</HEAD>

<BODY>

<form method = 'GET' action =

'http://localhost/CalcWebService/CalcService.asmx/Subtract'>

<p>First Number:

<input id=Text1 name = x type=text> </p>

<p>Second Number

<input id=Text2 name = y type=text> </p>

<p>

<input id=Submitl type=submit value=Submit> </p>

</form>

</B0DY>

</HTML>

To, как выглядит эта страница (можно сказать, пользовательский интерфейс нашей web-службы), показано на рис.15.12.

Рис.15.12. Форма HTML для передачи данных в web-службу

Код такой простой, что комментировать почти нечего. Отметим только два мо­мента. Обратите внимание, что мы в качестве получателя данных формы указали не только адрес страницы *.asmx, но и имя вызываемого метода — это вполне до­пускается. Кроме того, для каждого из текстовых полей мы при помощи атрибута name определили имя параметра, которому оно соответствует (х и у).

Результат произведенных web-службой вычислений представлен на рис.15.13.

Рис.15.13. Результат вычислений web-службы

Обратите внимание на адресную строку браузера: ее формат — верное доказа­тельство, что при передаче клиентом данных использовался метод HTTP-GET. Web-страницу CalcGET можно найти в подкаталоге Chapter 15.

Обмен данными при помощи SOAP

Гораздо более привлекательная альтернатива методам HTTP-GET и HTTP-POST — использование протокола подключения SOAP. Отличительной особенностью этого протокола является то, что с его помощью мы можем обмениваться сложными типа­ми данных. Мы можем передавать все те же типы данных, что и с помощью HTTP-GET и HTTP-POST, плюс дополнительные, которые представлены в табл.15.8. Вся передача информации производится в XML-совместимом формате.

Таблица 15.8. Типы, которые можно передавать при помощи SOAP

Подробное рассмотрение SOAP не входит в наши планы, однако наиболее важ­ные моменты мы все же отметим.

В первую очередь необходимо сказать о том, что SOAP изначально создавался как очень простой в обращении протокол. Кроме того, он, как и все, что связано с XML, абсолютно независим от платформ, операционных систем, языков програм­мирования и протоколов передачи данных. Например, данные SOAP мы можем передавать с помощью практически любых Интернет-протоколов (HTTP, SMTP и т. п.).

Любое описание в формате SOAP имеет два аспекта: информация, относящая­ся к самому сообщению в целом, и данные в формате XML, относящиеся к состав­ным частям данного сообщения.

Например, при использовании протокола SOAP для вызова метода Add() в на­шем примере определение этого метода в SOAP будет выглядеть так:

<message name="AddFloatsSoapIn">

<part name="parameters" element="sO:AddFloats" />

</message>

<message name="AddF1oatsSoap0ut">

<part name="parameters" element="sO:AddFloatsResponse"/>

</message>

Откроем код WSDL для нашей web-службы CalcWebService. Ближе к концу страницы мы можем найти три узла XML, которые описывают привязки (bindings) нашей web-службы к протоколам HTTP-GET, HTTP-POST и SOAP (рис.15.14). Эти записи определяют, что наша web-служба будет работать по каждому из этих трех протоколов.

Рис. 15.14. Привязки web-службы к протоколам подключения

Если открыть узел для привязки SOAP, там можно будет найти следующий код для метода Add() (обратите внимание на теги <input> и <output>).

<operation name="Add">

<soap:operation soapAction="http://tempuri.org/AddFloats" style="document" />

<input name="AddFloats">

<soap:body use="literal" />

</input>

<output name="AddF1oats">

<soap:body use="literal" />

</output>

</operation>

Было бы интересно разобраться с каждым тегом и атрибутом SOAP, однако, к сожалению, мы ограничены рамками данной книги. Возможно, вас утешит то, что спецификации SOAP найти совсем несложно (адреса URL с этой информаци­ей вообще помещаются в каждый файл WSDL, как мы могли убедиться). Кроме того, на практике, как мы уже видели и увидим в следующих примерах, требуемый код SOAP генерируется Visual Studio.NETавтоматически, и нет необходимости править его вручную.

Файл CalcService.asmx.csможно найти в подкаталоге Chapter 15.

Прокси-сборки для web-служб

Как мы могли убедиться, и передача данных клиентом (при работе по SOAP), и по­лучение их (при работе по любому протоколу подключения) производится в XML-совместимых форматах. Конечно, мы можем создавать клиента для web-служб с модулями компоновки кода в XML и модулями анализа возвращаемых узлов XML, но это довольно трудоемкое занятие. Кроме того, немного странно использовать на клиенте код, к примеру, С#, потом преобразовывать промежуточные результа­ты в XML, чтобы с их помощью вызывать опять-таки методы С#, но уже на web-службе. Как было бы удобно работать напрямую!

В общем, мечты уже стали реальностью. Рекомендуется не обращаться на web-службу из клиента напрямую (хотя этого никто тоже не запрещает), а вначале со­здать промежуточную прокси-сборку на привычном С# или другом языке про­граммирования и обращаться именно к ней — а она уже будет сама перенаправлять запросы на web-службу, получать возвращаемые результаты и передавать их вы­зывающему клиенту (который может быть обычным клиентом Windows Forms, или консольным, или клиентом ASP.NET— каким угодно). Самое замечательное во всем этом то, что прокси-сборка создается автоматически — при помощи VisualStudio.NETили специализированной утилиты wsdl.exe. А теперь посмотрим, как это делается.

Создание прокси-сборки при помощи утилиты wsdl.exe

Один из возможных способов сгенерировать прокси-сборку — воспользоваться утилитой wsdl.exe, входящей в состав .NET SDK. Минимально необходимый син­таксис ее очень прост:

wsdl.exe /out:C\ca1cproxy.cs http://1ocalhost/calcwebservice/calcservice.asmx?WSDL

Параметр /out — это, конечно, имя создаваемого исходного файла для прокси-сборки. Далее указывается адрес URL, по которомуможет быть получено описа­ние web-службы в формате WSDL (см. например, гиперссылку на рис.15.9). По умолчанию генерируется именно код С#, однако по нашему желанию может быть сгенерирован код и на Visual Basic.NET, инa JavaScript.NET. Дополнительные па­раметры командной строки для wsdl.exe приведены в табл.15.9.

Таблица 15.9. Параметры командной строки утилиты wsdl.exe

Как выглядит исходный код генерируемой прокси-сборки

В отличие от множества других прокси-сборок, предусмотренных в .NET (для ра­боты с COM, COM+ и т. п.), прокси-сборки для web-служб создаются в виде фай­лов с исходным кодом, а не откомпилированным. Поэтому будет особенно интерес­но заглянуть в генерируемый файл и посмотреть, что нами было создано. Выглядит все это так (обратите внимание на атрибут WebServiceBinding):

using System.Xml.Serialization;

using System;

using System.Web.Services.Protocols;

using System.Web.Services;

[System.Web.Services.WebServiceBindingAttribute(Name="ServicelSoap", Namespace="http://tempuri.org/")]

public class Servicel:

System.Web.Services.Protocols.SoapHttpClientProtocol

{

public Servicel()

{

this.Url = "http://localhost/calcwebservice/calcservice.asmx";

}

}

Как мы видим, информация о том, где находится web-служба, помещается при помощи свойства Url в объект класса прямо во время работы конструктора. Еще один важный момент: класс нашей прокси-сборки — это класс, производный от базового класса SoapHttpClientProtocol. В этом базовом классе и определены те воз­можности, с помощью которых наша прокси-сборка будет взаимодействовать с web-службой. Некоторые наиболее важные члены, унаследованные от SoapHttpClientProtocol, представлены в табл.15.10.

Таблица15.10. Наиболее важные унаследованные члены

В сгенерированной прокси-сборке автоматически генерируются определения методов для синхронного и асинхронного вызова каждого метода web-службы. Вещь очевидная, но мы все же ее озвучим специально: при синхронном вызове метода выполнение приостанавливается до возврата результатов с web-службы, а при асинхронном методе сразу после вызова управление возвращается клиенту. Когда выполнение метода, вызванного асинхронным способом, завершено, среда выполнения производит обратный вызов прокси-сборки. Реализация синхронно­го вызова метода Add() в сгенерированной прокси-сборке будет выглядеть так:

[System.Web.Services.Protocols.SoapMethodAttribute("http://tempuri.org/Add",

MessageStyle=System.Web.Services.Protocols.SoapMessageStyle.Pa rametersInDocument)]

public int Add(int x, int y)

{

object[] results = this.Invoke("Add", new object[] {x. y});

return ((int)results[0]));

}

Как мы видим, определение каждого метода, который нужно перенаправить на web-службу, помечается при помощи атрибута SoapMethod. У метода Add() такая же сигнатура, как у исходного метода Add () на web-службе. Это очень удобно: как толь­ко клиенту потребуется обратиться к методу на web-службе, он просто привычны­ми средствами вызывает этот метод в прокси-сборке, а та уже передает запрос на web-службу и возвращает клиенту полученные оттуда результаты.

Скорее всего, единственное изменение, которые мы захотим внести в сгенери­рованный исходный файл прокси-сборки, — поместить все его содержимое в спе­циальное пространство имен. Это можно сделать опять-таки автоматически, ука­зав имя пространства имен при помощи параметра /п утилиты wsdLexe.

Компилируем прокси-сборку

Прежде чем мы создадим клиента, работающего через прокси-сборку, нам необхо­димо эту прокси-сборку создать, то есть скомпилировать соответствующий файл. Это можно сделать при помощи Visual Studio.NET(через новый проект типа С# Code Library) или компилятора командной строки csc.exe. В любом случае не за­будем добавить ссылки на сборки System.Web.Services.dll иSystem.Xml.dll:

csc /r:system.web.services.dll /r:system.xml.dll /out:C:\Ca1cProxy.dll/t:librarycalcproxy.cs

В результате будет создана прокси-сборка в виде библиотеки типов с нашим прокси-классом (рис.15.15).

Рис.15.15. Прокси-сборка в окне ILDasm.exe

Создание клиента для работы через прокси-сборку

Основное назначение прокси-сборки — облегчить создание клиента. И действитель­но, клиент, работающий через прокси-сборку, создается не просто, а очень просто. Он может быть любым — обычным клиентом Windows Forms, консольным или кли­ентом ASP.NET. Мы создадим самого простого клиента — консольного:

// Не забудьте добавить ссылку на System.Web.Services.dll!

namespace WebServiceConsumer

{

using System;

// Пространство имен нашей прокси-сборки

using TheCalcProxy;

public class WebConsumer

{

public static int Main(string[] args)

{

// Работаем с web-службой

Servicel w = new Service();

Console.WriteLine("lOO + 100 is {0}", w.Add(1OO, 100));

try

{

w.Divide(0, 0);

}

catch(DivideByZeroException e)

{

Console.WriteLine(e.Message);

}

return 0;

}

}

}

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

Рис. 15.16. Консольный клиент web-службы.

Код приложения CalcClient можно найти в подкаталоге Chapter 15.

Создание прокси-сборки в Visual Studio.NET

Создавать прокси-сборки можно и при помощи утилиты wsdl.exe, и прямо в Visual Studio.NET. Работа с wsdl.exe более трудоемка, но у этой утилиты есть одно суще­ственное преимущество: мы можем указать протокол подключения, который бу­дет использоваться прокси-сборкой (HTTP-GET, HTTP-POST или SOAP). Visual Studio.NETпозволяет генерировать только прокси-сборки, работающие по прото­колу SOAP. Впрочем, если мы создаем прокси-сборку, то в подавляющем боль­шинстве случаев будем использовать для нее именно SOAP. Поэтому прокси-сбор­ки удобнее создавать в VisualStudio.NET.

Давайте воспользуемся этой возможностью. Мы создадим очень простого кли­ента Windows Forms с элементарным интерфейсом, который позволит пользова­телю вводить два числа и передавать их четырем арифметическим методам Add(), Subtract() и т. п., которые будут перенаправлять данные через прокси-сборку на web-службу для вычислений. Создание прокси-сборки производится автоматичес­ки, нам нужно только добавить в наш проект web-ссылку (рис.15.17).

Рис.15.17. Добавление web-ссылки

Отроется диалоговое окно Add Web Reference (Добавить web-ссылку), в котором мы сможем указать адрес URL к файлу *.asmx и просмотреть различную информа­цию об web-службе (рис.15.18).

После этого выберем вкладку View Contract (Просмотреть контракт) и добавим ссылку. В окне Solution Explorer появится новый узел web-ссылки (рис.15.19).

После этого мы уже можем напрямую работать в нашем проекте с классом прокси-сборки (он будет называться Servicel). Обратите внимание, что в качестве на­звания пространства имен прокси-сборки выбирается имя компьютера, на кото­ром расположена web-служба:

using localhost:

public class mainForm : System.Windows.Forms.Form

{

protected void btnAdd_Click (object sender. System.EventArgs e)

{

localhost.Servicel w = new localhost.Service1();

int ans = w.Add(int.Parse(txtNumb1.Text), int.Parse(txtNumb2.Text));

lblAns.Text = ans.ToString();

}

}

Рис.15.18. Диалоговое окно Add Web Reference (Добавить web-ссылку)

Рис.15.19. Web-ссылка в окне Solution Explorer

Код приложения WinFormsCalcClient можно найти в подкаталоге Chapter 15.

Пример более сложной web-службы (и ее клиентов)

Однако мы намного зациклились на элементарном примере web-службы, выпол­няющей функции калькулятора. Возможности web-служб можно представить го­раздо лучше, если реализовать методы, которые будут возвращать (конечно, только при помощи SOAP) объекты ADO.NETDataSet, объекты пользовательских клас­сов и массивы объектов. Созданием такой web-службы мы и займемся.

Мы начнем новый проект С# на основе шаблона Web Service и назовем его CarsWebService. Первое, что мы сделаем — создадим метод web-службы GetAllCars(), который будет возвращать объект DataSet с полным набором записей из таблицы Inventory той самой базы данных Cars, с которой мы работали в главе 13. Код для этого метода может выглядеть так:

// Возвращаем все записи из таблицы Inventory

public DataSet GetAllCars()

{

// Заполняем объект DataSet данными из таблицы Inventory

SqlConnection sqlConn = new SqlConnection();

sqlConn.ConnectionString = "data source=.; initial catalog=Cars;" + "user id=sa;

password=";

SqlDataAdapter dsc = new SqlDataAdapter("Select * from Inventory", sqlConn);

DataSet ds = new DataSet();

dsc.Fill(ds, "Inventory");

return ds;

}

Теперь, если мы создадим клиента Windows Forms и добавим в него web-ссыл­ку на web-службу, мы сможем вызывать метод web-службы GetAllCar(), напри­мер, для заполнения элемента управления DataGrid данными из таблицы Inventory (рис.15.20):

private void mainForm_Load(object sender. System.EventArgs e)

{

bigmanu.Service1 s = new bigmanu.Service1();

DataSet ds = s.GetAllCars();

dataGrid1.DataSource = ds.Tables["Inventory"];

}

Рис.15.20. Получение с web-службы объекта DataSet

Сериализация пользовательских типов

Протокол SOAP умеет передавать XML-представления объектов пользовательских типов с сохранением их внутреннего состояния. Чтобы убедиться в этом на приме­ре, давайте добавим в наше приложение CarsWebService новый класс Саг. Он бу­дет очень простым: две переменные, определенные как publiс — для хранения ин­формации о прозвище машины и о ее максимальной скорости, и перегруженный конструктор, который позволяет установить эти значения. Также обязательно добавим одну очень важную деталь — атрибут Xml Include (определенный в простран­стве имен System.Xml .Serialization). Определение нашего класса будет выглядеть так:

namespace CarsWebService

{

using System;

using System.Xml.Serialization;

[XmlInclude(typeof(Car))]

public class Car

{

public Car(){}

public Car(string n, int s)

{petName = n; maxSpeed = s;}

public string petName;

public int maxSpeed;

}

}

Процесс сохранения объекта вместе со всем внутренним состоянием (в данном случае в формате XML) называется сериализацией. Однако сериализация объекта воз­можна лишь в том случае, если класс этого объекта был помечен атрибутом Xml Include.

А теперь мы определим как private массив типа ArrayList (он будет называться carList) и добавим в него несколько объектов Саг — прямо в конструкторе класса нашей web-службы:

public Service1()

{

InitializeComponent();

// Добавляем объекты Car

carList.Add(new Car("Zippy", 170));

carList.Add(new Car("Fred", 80));

carList.Add(new Car("Sally", 40));

}

После этого мы определим в web-службе два метода. GetCarList() будет возвра­щать весь массив carList целиком. GetACarFromList () будет возвращать конкретный объект Саг по его номеру в массиве. Вот определения каждого из этих методов:

// Возвращаем конкретный объект Саг

[WebMethod]

public Car GetACarFromList(int carToGet)

{

if(carToGet <= carList.Count)

{

return (Car) carList[carToGet];

}

throw new IndexOutOfRangeException();

}

// Возвращаем массив объектов Саг целиком

[WebMethod]

public ArrayList GetCarList()

{

return carList;

}

Настраиваем клиента web-службы

Службу мы уже создали, осталось создать клиента, который к ней будет обращаться. Мы воспользуемся уже готовым клиентом из предыдущего примера (с элементом управления DataGrid). Единственное, что обязательно надо сделать, раз web-служ­ба изменилась, — это обновить web-сслыку, например, из окна Solution Explorer. Мы добавим к графическому интерфейсу нашего клиента две кнопки (для вызова каж­дого из методов web-службы) и текстовое поле для ввода номера объекта Саr в мас­сиве. Код обработчика события Click для кнопки, которая будет вызывать метод GetACarFromList, будет таким:

protected void btnGetCar_Click (object sender. System.EventArgs e)

{

try

{

bigmanu.Car c;

bigmanu.Service1 cws = new bigmanu.Service1();

с = cws.GetACarFromList(int.Parse(txtCarToGet.Text));

MessageBox.Show(c.petName. "Car " + txtCarToGet.Text + " is named:");

cws.Dispose();

}

catch

{

MessageBox.Show("No car with that number...");

}

}

Результат запуска нашего клиента представлен на рис.15.21. Помните, что мы извлекаем информацию об объекте Саг из массива, а не из таблицы Inventory, кото­рая отображается в DataGrid!

Рис. 15.21. Получаем объект Саг из массива ArrayList на web-службе

Обработчик события Click для второй кнопки будет выглядеть так:

private void btnArrayList_Click(object sender. System.EventArgs e)

{

bigmanu.Service1 cws = new bigmanu.Service1();

object[] objs = cws.GetCarList();

string petNames = "";

// Переносин все данные из массива в строковую переменную

for(int i=0; i < objs.Length; i++)

{

bibmanu.Car с = (bigmanu.Car)objs[i];

petNames += c.petName + "\n";

}

MessageBox.Show(petNames, "Pet names for cars in array list:");

cws.Dispose();

}

Создание типов для сериализации (некоторые уточнения)

При сериализации объекта в формате XML обычно является принципиальным сохранение внутреннего состояния этого объекта — чтобы его можно было потом восстановить (например, на клиенте) и продолжить с ним работу. Чтобы среда выполнения могла сериализовать внутренние данные объекта, ей необходимо по­лучить к ним доступ. Однако если мы определили внутренние переменные как pri vate, то получить к ним доступ среда выполнения не сможет. Как мы помним, в нашем примере мы предусмотрительно определили обе переменные как public:

[XmlInc1ude(typeof(Car))]

public class Car

{

public string petName;

public int maxSpeed;

}

}

Предположим, что мы определили их как private (как положено с точки зрения культуры программирования):

[XmlInclude(typeof(Саг))]

public class Car

{

public Car(){}

public Car(string n, int s)

{ petName = n: maxSpeed = s; }

// Попробуем меня сериализовать?

private string petName;

private int maxSpeed; }

Если мы после этого вызовем метод GetCarList(), то в массиве также обнару­жатся три объекта Саг. Однако ни для одного из этих объектов не сохранится ин­формация о внутреннем состоянии! Проблема решается просто: надо либо опреде­лять переменные как public, либо создать свойства для доступа к переменным, определенным как private:

[Xmllnclude(typeof(Car))]

public class Car

{

private string petName;

private int maxSpeed;

public string PetName

{

get { return petName; }

set { petName = value; }

}

public int MaxSpeed

{

get { return maxSpeed; }

set { maxSpeed = value; }

}

}

В принципе, создание объектов, к которым можно будет обратиться через web-службу, не сильно отличается от создания обычных объектов С#. Главное — не забыть пометить такие объекты атрибутом [XmlInclude] и обеспечить возможность доступа к данным, определенным как private.

Файл CarsWebService.asmx.cs можно найти в подкаталоге Chapter 15.

Протокол обнаружения web-службы

Последнее, о чем пойдет речь в этой глайе — о службе обнаружения (discovery service) для web-службы и о файлах *.disco, которые используются для ее настройки.

Когда клиент обращается к web-службе, первое, что он должен сделать — убе­диться, что web-служба по данному адресу существует. В принципе это можно сде­лать программным образом — в библиотеке базовых классов .NET предусмотрены для этого соответствующие типы. Однако стандартное средство сделать это — ис­пользовать службу обнаружения. Кроме того, служба обнаружения также необхо­дима многим средствам разработки (например, она используется при добавлении web-ссылки в проект С#).

Информация о всех web-службах в конкретном виртуальном каталоге и его подкаталогах хранится в файле *.disco. Этот файл создается Visual Studio.NET полностью автоматически. Например, при создании нового проекта на основе шаб­лона Web Service исходный код файла *.disco будет таким:

<?xml version="1.0" ?>

<dynamicDiscovery xmlns="urn:schemas-dynamicdiscovery:disco.2000-03-17">

<exclude path="_vti_cnf" />

<exclude path="_vti_pvt" />

<exclude path="_vti_log" />

<exclude path="_vti_script" />

<exclude path="_vti_txt" />

<exclude path="Web References" />

</dynamicDiscovery>

Тег <dynamicDiscovery> определяет, что в ответ на запрос к службе обнаружения данный файл *.disco должен быть обработан средой выполнения ASP.NETна сер­вере и клиенту должен быть возвращен ответ на его запрос (ответ, конечно, будет в формате XML), сгенерированный на основе файла *.disco. Например, если мы обра­тимся из Internet Explorer к файлу *.disco для нашего CarsWebService, то среда выполненияASP.NETсгенерирует нам ответ в формате XML, который представлен на рис.15.22.

Рис.15.22. Информация о web-службах нашего виртуального каталога

Добавление новой web-службы

Пока в нашем виртуальном каталоге есть только одна web-служба, к которой обра­щаются по адресу /CarsWebService/Service1.asmx. Поэтому содержимое файла *.disco вполне очевидно. Однако что произойдет, если мы добавим в наш виртуальный каталог новую web-службу? Давайте так и сделаем.

Откроем в Visual Studio.NETнаш проект CarWebService и добавим в него новую web-службу. Проще всего это сделать при помощи меню Project(Проект) ► Add Web Service (Добавить web-службу). Новую web-службу мы назовем MotorBikes.asmx (рис.15.23).

Рис.15.23. В одном проекте Web Service может быть несколько web-служб

В классе MotorBikes будет определен единственный метод:

[WebMethod]

public string GetBikerDesc()

{

return "Name: Tiny. Wight: 374 pounds.";

}

После перекомпиляции нашего проекта файл *.disco вернет нам информацию уже о двух web-службах (рис.15.24).

Рис.15.24. Файл *.disco возвращает информацию обо всех web-службах виртуального каталога

Конечно, просмотр необработанного кода XML в браузере — это не самый луч­ший способ получения сведений о web-службах. В библиотеке базовых классов .NET предусмотрены типы, которые позволяют работать с данными, возвращае­мыми службой обнаружения. Кроме того, эта информация необходима мастеру Add Web Reference и многим средствам разработки.

Подведение итогов

В этой главе были рассмотрены основные принципы и приемы построения web-служб. Вначале мы рассмотрели наиболее важные пространства имен и типы, ко­торые используются для создания web-служб, а также основные технологии: служ­бы обнаружения, язык описания web-служб (WSDL) и протокол подключения (HTTP-GET, HTTP-POST и SOAP).

После того как web-служба создана и мы определили в ней доступные для вне­шних клиентов методы при помощи атрибута [WebMethod], проще всего реализовывать клиентов таким образом, чтобы они обращались не к web-службе напрямую, а к промежуточной прокси-сборке, которая будет перенаправлять вызовы мето­дов на web-службу. Прокси-сборку можно сгенерировать при помощи утилиты wsdl.exe (для использования любого протокола подключения), либо из Visual Studio.NET(только для SOAP). В самом конце были рассмотрены вопросы передачи объектов пользовательских типов посредством SOAP и определения классов как пригодных для сериализации в формате XML при помощи атрибутаXml Include.