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-службы вам потребуется также реализовать дополнительную поддерживающую инфраструктуру. К ней относятся:
протокол подключения (HTTP-GET, HTTP-POST или SOAP);
служба описания — description service (чтобы клиент мог получить информацию о том, что делает эта web-служба);
служба обнаружения — discovery service (чтобы клиент мог получить информацию о том, что 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 (от discovery), опять-таки в формате XML. Мы познакомимся с синтаксисом этих файлов в последней части этой главы.
Обзор пространств имен web-служб
Как вы уже, наверное, догадываетесь, разработчики .NET заготовили для нас множество типов, которые могут быть использованы как для построения самих web-служб, так и для создания необходимой инфраструктуры. Эти типы определены в пространствах имен, представленных в табл.15.1.
Таблица15.1. Пространства имен для web-служб
Пространство имен System.Web.Services
В большинстве проектов по созданию web-служб единственные типы, с которыми нам придется взаимодействовать напрямую — это типы пространства имен System.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.
- Начальное руководство по разработкеweb-приложений и asp.Net.
- Разработка web-приложений и asp.Net
- Web-приложения и web-серверы
- Что такое виртуальные каталоги?
- Структура документа html
- Форматирование текста средствами html
- Заголовки html
- Html-редактор Visual Studio.Net
- Разработка форм html
- Создаем пользовательский интерфейс
- Добавление изображений
- Клиентские скрипты
- Пример клиентского скрипта
- Реализация проверки введенных пользователем данных
- Передаем данные формы (методы geTиPost)
- Синтаксис строки запроса http
- Создание классической страницы asp
- Принимаем данные, переданные методом post
- Первое приложение asp.Net
- Некоторые проблемы классических asp
- Некоторые преимущества asp.Net
- Пространства имен asp.Net
- Наиболее важные типы пространства имен System.Web
- Приложение и сеанс подключения пользователя
- Создание простого web-приложения на с#
- Исходный файл *.Aspx
- Файл web.Config
- Исходный файл Global.Asax
- Простой код asp.Net на с#
- Архитектура web-приложения asp.Net
- Тип System.Web.Ui.Page
- Связка *.Aspx/Codebehind
- Свойство Page. Request
- Свойство Page.Response
- Свойство Page.Application
- Отладка и трассировка приложений asp.Net
- Элементы управления WebForm
- Создание элементов управления WebForm
- Иерархия классов элементов управления WebForm
- Виды элементов управления WebForm
- Базовые элементы управления WebForm
- Группа переключателей
- Текстовое поле для ввода нескольких строк с полосой прокрутки
- Элементы управления с дополнительными возможности
- Элемент управления Calendar
- Элемент управления AdRotator (баннерная рулетка)
- Элемент управления для работы с источниками данных
- Элемент управления DataGrid
- Еще немного об источниках данных
- Элементы управления для проверки вводимых пользователем данных
- Обработка событий элементов управления WebForm
- Подведение итогов
- Web-cлужбы