Сообщений 0    Оценка 115        Оценить  
Система Orphus

Использование XML совместно с SQL

Часть 2. MS SQL Server 2000 в качестве Web-службы

Автор: Алексей Ширшов
Источник: RSDN Magazine #1-2004
Опубликовано: 03.10.2004
Исправлено: 10.12.2016
Версия текста: 1.0
Предисловие
Настройка IIS
Содержание wsdl-файла
Секция types
Секция message
Секция portType
Формы сообщений
Секция binding
Секция service
Создание Web-методов
XML objects
Dataset objects
Single dataset
Web-методы
Тестируем Web-сервис
XMLHTTP
SOAP Toolkit
.NET Framework
Поддержка SQLXML в .NET Framework
Тестовый проект
Цели и ограничения
Дизайн
Реализация
Заключение
Литература

Предисловие

Эту статью я собирался написать очень давно. Около года прошло с момента появления первой части «Использование XML совместно с SQL», а это – очень большой срок для современной IT-индустрии. Сейчас уже доступна первая бета новой версии SQL Server-а под кодовым названием «Yukon». В нем будет просто сумасшедшее количество нововведений, связанных с XML (и не только), одним из которых является возможность напрямую обращаться к серверу через Internet. Как вы знаете, в текущей версии это невозможно: взаимодействие с сервером происходит через специальное ISAPI-расширение, которое является частью пакета SQLXML. Вообще, по сравнению с Yukon (или Oracle9i), поддержка XML в SQL Server 2000 очень слаба. По существу, она ограничивается двумя конструкциями: FOR XML и OPENXML. Вся остальная функциональность реализуется с помощью SQLXML[1]. Все, что предоставляет SQLXML, как-то: шаблоны, запросы в URL, запросы XPath в конечном счету превращаются в ту или иную форму SQL-запроса, вида:

      select ... from [table] ... for xml explicit

Вызов хранимых процедур и функций через Internet – также целиком и полностью заслуга SQLXML: с помощью ISAPI-расширения он перехватывает SOAP-запросы, транслирует их в вызовы соответствующих функций и процедур SQL Server-а и возвращает результат в виде XML. Трансформация выбранного recordset-а в XML происходит на основе информации конфигурационного файла (расширение ssc), но обо всем по порядку.

Настройка IIS

Для того чтобы стал возможен вызов хранимых процедур и функций как Web-методов, необходимо создать и настроить виртуальную директорию Internet-сервера. Это выполняется с помощью консоли «IIS Virtual Directory Management for SQLXML 3.0». Я не буду рассматривать все шаги этого процесса, так как для всех типов запросов (soap, templates, dbobject и schema) он одинаков. Подробную информацию об этом можно получить из документации по SQLXML 3.0 и в [1]. Рассмотрим лишь, как настроить запросы типа soap. На рисунке 1 приведен пример.


Рисунок 1. Создание Web-сервиса.

Здесь имя виртуальной директории – «srv». Имя Web-сервиса – webserv, тип директории – soap, имя хоста – dcit06. Кроме этого, необходимо указать физический путь до папки, где будут располагаться конфигурационный файл и файл описания Web-сервиса. После того, как вы нажмете кнопку «Save», SQLXML создаст wsdl-файл, в котором будет содержаться описание Web-сервиса, и конфигурационный файл ssc, в котором будут содержаться описания всех Web-методов и их параметров. Файл ssc предназначен только для внутренних служебных нужд SQLXML, его формат нигде не описан и в любой момент может измениться.

На вкладке Settings не забудьте поставить галочку напротив опции «Allow POST».

Содержание wsdl-файла

WSDL – это специальный формат, предназначенный для описания Web-сервисов, их методов, параметров и используемых протоколов. Это обычный XML-документ с корневым тегом definitions, содержащий описания типов, параметров, операций, протоколов и сервисов. Они подробно будут описаны далее. SQLXML генерирует файл в формате WSDL 1.1, который имеет статус ноты (как и SOAP 1.1).

ПРИМЕЧАНИЕ

Если вы не знакомы с терминологией наименования этапов обработки документов консорциума w3c, рекомендую ознакомиться с документом http://www.w3.org/Consortium/Process.

В данный момент уже практически завершается работа по созданию спецификации WSDL 1.2. Более подробную информацию можно получить по адресу http://www.w3.org/2002/ws.

Файл WSDL условно можно поделить на две части: абстрактную и конкретную часть. Первая описывает типы, параметры и операции и не касается конкретных протоколов и конечных точек (endpoints) или сервисов. WSDL предназначен не только для работы по протоколу SOAP, так что описание конкретного транспорта выделается в отдельные, конкретные элементы, описание которых находится в второй части WSDL файла. Рассмотрим по порядку фрагменты абстрактной и конкретной частей, которые далее в статье будут называться секциями.

Секция types

Эта секция содержит набор схем в формате XML Schema, каждая из которых описывает абстрактные типы, не зависящие от какого-либо языка или машины. Эта секция не является обязательной и может быть опущена, если Web-сервис не нуждается в дополнительных типах (как правило, это составные типы – complex types) и вполне обходится стандартными, определенными в XML Schema.

SQLXML определяет четыре схемы типов:

http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types. Эта схема описывает базовые типы, используемые при построении сообщений.

Простой тип nonNegativeInteger – целочисленный тип без знака в диапазоне от 0 до 2147483647.

Атрибут IsNested булевого типа.

Сложный тип SqlRowSet – используется в случае, когда результирующее сообщение имеет тип DataSet object. Более подробно о типах результирующих сообщений см. раздел «Создание Web-методов».

Сложный тип SqlXml – используется в случае, когда результирующее сообщение имеет тип XML objects.

Простой тип SqlResultCode - целочисленный тип без знака в диапазоне от 0 до 2147483647, который используется для описания возвращаемого хранимой процедурой значения.

http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types/SqlMessage. Эта схема определяет единственный тип, который описывает сообщение от SQL Server-а в случае ошибки.

Сложный тип SqlMessage – содержит поля для описания кода ошибки, номера строки, строкового представления ошибки и других параметров.

http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types/SqlResultStream. Эта схема определяет единственный сложный тип, который описывает любой ответ от сервера.

Сложный тип SqlResultStream – содержит одно и более вхождений полей следующих типов: SqlRowSet, SqlXml, SqlMessage и SqlResultCode.

Пространство имен конкретного Web-сервиса, которое импортирует все вышеперечисленные пространства имен и описывает типы, необходимые конкретным Web-методам. В данный момент эта схема не содержит ни одного описания, так как пока Web-сервис не определил каких-либо Web-методов.

В качестве примера приведу фрагмент WSDL-файла, сгенерированного при создании виртуальной директории.

Типы SQLXML:
<wsdl:types>
  <xsd:schema targetNamespace="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types"
    elementFormDefault="qualified" attributeFormDefault="qualified">
    <xsd:import namespace="http://www.w3.org/2001/XMLSchema"/>
    <xsd:simpleType name="nonNegativeInteger">
      <xsd:restriction base="xsd:int">
        <xsd:minInclusive value="0"/>
      </xsd:restriction>
    </xsd:simpleType>
    <xsd:attribute name="IsNested" type="xsd:boolean"/>
    <xsd:complexType name="SqlRowSet">
      <xsd:sequence>
        <xsd:element ref="xsd:schema"/>
        <xsd:any/>
      </xsd:sequence>
      <xsd:attribute ref="sqltypes:IsNested"/>
    </xsd:complexType>
    <xsd:complexType name="SqlXml" mixed="true">
      <xsd:sequence>
        <xsd:any/>
      </xsd:sequence>
    </xsd:complexType>
    <xsd:simpleType name="SqlResultCode">
      <xsd:restriction base="xsd:int">
        <xsd:minInclusive value="0"/>
      </xsd:restriction>
    </xsd:simpleType>
  </xsd:schema>
  <xsd:schema targetNamespace="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types/SqlMessage"
    elementFormDefault="qualified" attributeFormDefault="qualified">
    <xsd:import namespace="http://www.w3.org/2001/XMLSchema"/>
    <xsd:import namespace="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types"/>
    <xsd:complexType name="SqlMessage">
      <xsd:sequence minOccurs="1" maxOccurs="1">
        <xsd:element name="Class" type="sqltypes:nonNegativeInteger"/>
        <xsd:element name="LineNumber" type="sqltypes:nonNegativeInteger"/>
        <xsd:element name="Message" type="xsd:string"/>
        <xsd:element name="Number" type="sqltypes:nonNegativeInteger"/>
        <xsd:element name="Procedure" type="xsd:string"/>
        <xsd:element name="Server" type="xsd:string"/>
        <xsd:element name="Source" type="xsd:string"/>
        <xsd:element name="State" type="sqltypes:nonNegativeInteger"/>
      </xsd:sequence>
      <xsd:attribute ref="sqltypes:IsNested"/>
    </xsd:complexType>
  </xsd:schema>
  <xsd:schema targetNamespace="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types/SqlResultStream"
    elementFormDefault="qualified" attributeFormDefault="qualified">
    <xsd:import namespace="http://www.w3.org/2001/XMLSchema"/>
    <xsd:import namespace="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types"/>
    <xsd:import namespace="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types/SqlMessage"/>
    <xsd:complexType name="SqlResultStream">
      <xsd:choice minOccurs="1" maxOccurs="unbounded">
        <xsd:element name="SqlRowSet" type="sqltypes:SqlRowSet"/>
        <xsd:element name="SqlXml" type="sqltypes:SqlXml"/>
        <xsd:element name="SqlMessage" type="sqlmessage:SqlMessage"/>
        <xsd:element name="SqlResultCode" type="sqltypes:SqlResultCode"/>
      </xsd:choice>
    </xsd:complexType>
  </xsd:schema>
  <xsd:schema targetNamespace="http://dcit06/srv/webserv" elementFormDefault="qualified" attributeFormDefault="qualified">
    <xsd:import namespace="http://www.w3.org/2001/XMLSchema"/>
    <xsd:import namespace="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types"/>
    <xsd:import namespace="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types/SqlMessage"/>
    <xsd:import namespace="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types/SqlResultStream"/>
  </xsd:schema>
</wsdl:types>

Секция message

Эта секция содержит описания параметров Web-методов. В текущем WSDL-файле ее нет, так как мы пока еще не определили никакого метода. Вообще, секция message состоит из набора частей (parts), каждая из которых описывает конкретный параметр. Часть может быть и одна, в этом случае используется несколько отличный синтаксис, при котором задается конкретный элемент части, вместо перечисления нескольких типов. Ниже приведен пример одинаковых параметров.

Фрагмент WSDL-определения параметров с помощью встроенных типов XML Schema
<wsdl:message name="mes1">
  <wsdl:part name="param1" type="xsd:int"/>
  <wsdl:part name="param2" type="xsd:int"/>
</wsdl:message>
Фрагмент WSDL-определения параметров с помощью собственного типа
<wsdl:types>
  <xsd:schema targetNamespace="http://tempuri.org"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:tns="http://tempuri.org">
    <xsd:complexContent name="params">
      <xsd:sequence>
        <xsd:element name="param1" type="xsd:int"/>
        <xsd:element name="param2" type="xsd:int"/>
      </xsd:sequence>
    </xsd:complexContent>
  </xsd:schema>
</wsdl:types>
<wsdl:message name="mes1">
  <wsdl:part name="params" type="tns:param"/>
</wsdl:message>
Фрагмент WSDL-определения параметров с помощью элемента собственного типа.
<wsdl:types>
  <xsd:schema targetNamespace="http://tempuri.org"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:tns="http://tempuri.org">
    <xsd:element name="params">
      <xsd:complexContent>
        <xsd:sequence>
          <xsd:element name="param1" type="xsd:int"/>
          <xsd:element name="param2" type="xsd:int"/>
        </xsd:sequence>
      </xsd:complexContent>
    </xsd:element>
  </xsd:schema>
</wsdl:types>
<wsdl:message name="mes1">
  <wsdl:part name="params" element="tns:params"/>
</wsdl:message>

В SQLXML принят последний стиль описания параметров, так как в нем используется документ-литеральная форма SOAP-сообщений. О формах сообщений мы поговорим чуть позже.

Секция portType

Последняя секция, где описываются абстрактные параметры Web-сервиса. Эта секция является обязательной и содержит неопределенное количество необязательных разделов operation. Операции описывают конкретные Web-методы. Так как у нас пока нет Web-методов, то и количество разделов operation равно нулю.

У каждой операции есть имя и набор входных, выходных и ошибочных параметров, представленных элементами input, output и fault соответственно. Входные и выходные параметры могут образовывать различные типы операций: запрос/ответ, запрос, ответ и ответ/запрос. Это достигается с помощью соответствующей комбинации элементов input и output. Например, следующий псевдофрагмент описывает схему взаимодействия запрос/ответ (вопросительный знак обозначает необязательность элемента, звездочка – неопределенное количество элементов):

<wsdl:portType .... >
  <wsdl:operation name="nmtoken" parameterOrder="nmtokens">
    <wsdl:input name="nmtoken"? message="qname"/>
    <wsdl:output name="nmtoken"? message="qname"/>
    <wsdl:fault name="nmtoken" message="qname"/>*
    </wsdl:operation>
</wsdl:portType >

Формы сообщений

Спецификация SOAP старше WSDL. На момент ее разработки и утверждения у программистов не было механизмов описания Web-сервисов, которые можно было бы использовать без предварительного изучения протокола. Для обхода этой проблемы спецификация SOAP определяет специальный формат обмена сообщениями, известный как RPC/encoded. RPC – определяет форму сообщения, а encoded – методы сериализации данных в тело SOAP-сообщения. Encoded сериализация подробно описана в разделе 5 спецификации [6].

С выходом WSDL появилась возможность описывать формат сообщений с помощью XML Schema. Эта форма получила название document/literal. Document означает, что тело SOAP-сообщения может и не представлять собой вызов метода. Это может быть просто набор элементов, соответствующих какому-либо бизнес-документу. Literal означает, что формат сообщения полностью определяется секцией types, т.е. схемой документа. Никаких особых правил, как в случае encoded сериализации, здесь не применяется.

Возможны также и комбинации этих форматов: RPC/literal, document/encoded. Рассмотрим их по порядку.

RPC-стиль подразумевает, что сообщение содержит только один элемент верхнего уровня (непосредственный дочерний элемент SOAP:Body), название которого соответствует имени удаленного метода. Имя метода определяется именем элемента operation секции portType WSDL-документа. Пространство имен этого элемента определяется значением атрибута namespace элемента связывания SOAP:Body. Каждый вложенный в него элемент представляет параметр метода, имя которого определяется именем части в секции message WSDL-документа. Таким образом, частей может быть несколько, так как их количество равно количеству параметров метода. Элемент, представляющий параметр, должен быть неквалифицированным, т.е. не должен принадлежать никакому или пустому пространству имен. Содержимое элементов, представляющих параметры, определяется типом сериализации: encoded или literal.

Document стиль определяет, что сообщение будет в точности соответствовать схеме, описанной в разделе types. Количество элементов верхнего уровня может быть любым. Имя элемента верхнего уровня определяется схемой, а не именем операции, как в RPC-стиле. Количество частей, ссылающихся на тип в разделе message – не более одной. Если часть ссылается на элемент, их может быть несколько. Содержимое элементов, представляющих параметры, определяется типом сериализации: encoded или literal.

Encoded-сериализация слишком сложна и объемна для этой статьи. Интересующихся отсылаю к разделу 5 спецификации [6]. Что касается WSDL-документа, то используя этот тип сериализации, вы не можете указывать конкретных элементов в части секции message. Т.е. допустим только синтаксис указания типа.

Эквивалент функции void foo(int i);- RPC/encoded
<message name="fooIn"> 
  <part name="i" type="xsd:int" />
</message>
<message name="fooOut" />
<portType name="Port1">
  <operation name="foo">
    <input message="fooIn" /> 
    <output message="fooOut" /> 
  </operation>
</portType>

Тело SOAP сообщения

  <SOAP-ENV:Body>
    <m:foo xmlns:m="(TargetNamespace)" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <i xsi:type="xsd:int">0</i>
    </m:foo>
  </SOAP-ENV:Body>
Эквивалент функции void foo(int i);- document/encoded
<message name="fooIn">
  <part name="i" type="xsd:int"/>
</message>
<message name="fooOut"/>
<portType name="Port1">
  <operation name="foo">
    <input message="fooIn" /> 
    <output message="fooOut" /> 
  </operation>
</portType>

Тело SOAP-сообщения

  <SOAP-ENV:Body>
    <SOAP- ENC:int>0</SOAP- ENC:int >
  </SOAP-ENV:Body>

Literal-сериализация намного проще encoded, так как содержимое документа определяется только схемой. Части сообщения могут быть представлены либо одним типом, либо набором элементов. В первом случае, само содержимое элемента soap:body кодируется в соответствии с типом части, а во втором случае – тело SOAP-сообщения содержит набор описанных элементов.

Эквивалент функции bool foo(string s,int i); - RPC/literal
<s:schema>
  <s:element name="str" type="xsd:string"/>
  <s:element name="int" type="xsd:int"/>
</s:schema>
<message name="fooIn">
  <part name="s" element="m:str" />
  <part name="i" element="m:int" />
</message>
<message name="fooOut">
  <part name="retval" type="xsd:boolean" /> 
</message>
<portType name="Port1">
  <operation name="foo">
    <input message="fooIn" /> 
    <output message="fooOut" /> 
  </operation>
</portType>

Тело SOAP сообщения

<SOAP-ENV:Body>
  <m:foo xmlns:m="(TargetNamespace)">
    <m:str>some string</m:str>
    <m:int>0</m:int>
    </m:foo>
</SOAP-ENV:Body>
Эквивалент функции bool foo(string s,int i); - document/literal с использованием элементов
<s:schema>
  <s:element name="some_document">
    <s:complexType name="unimportant">
      <s:sequence>
        <s:element name="str" type="xsd:string"/>
        <s:element name="int" type="xsd:int"/>
      </s:sequence>
    </s:complexType>
  </s:element>
</s:schema>
<message name="fooIn">
  <part name=" unimportant " element="m:some_document" />
</message>
<message name="fooOut">
  <part name="retval" type="xsd:boolean" /> 
</message>
<portType name="Port1">
   <operation name="foo">
      <input message="fooIn" /> 
      <output message="fooOut" /> 
   </operation>
</portType>

Тело SOAP-сообщения

<SOAP-ENV:Body>
  <m:some_document>
    <m:str>some string</m:str>
    <m:int>0</m:int>
  </m:some_document>
</SOAP-ENV:Body>
Эквивалент функции bool foo(string s,int i); - document/literal с использованием типа
<s:schema>
  <s:complexType name="unimportant">
    <s:sequence>
      <s:element name="str" type="xsd:string"/>
      <s:element name="int" type="xsd:int"/>
    </s:sequence>
  </s:complexType>
</s:schema>
<message name="fooIn">
  <part name="unimportant" type="m:unimportant" />
</message>
<message name="fooOut">
  <part name="retval" type="xsd:boolean" /> 
</message>
<portType name="Port1">
  <operation name="foo">
    <input message="fooIn" /> 
    <output message="fooOut" /> 
  </operation>
</portType>

Тело SOAP-сообщения

<SOAP-ENV:Body>
  <m:str>some string</m:str>
  <m:int>0</m:int>
</SOAP-ENV:Body>

Стили и способы сериализации задаются в секции binding WSDL-документа (см. ниже).

Стиль document/literal постепенно вытесняет остальные стили, так как он намного проще в понимании и реализации. Кроме этого, этот стиль позволяет точно определить содержимое документа по его схеме, тогда как при использовании стиля RPC/encoded нужно знать некоторые правила сериализации.

ПРИМЕЧАНИЕ

В новой спецификации SOAP 1.2, которая уже стала рекомендацией, правила RPC-сериализации несколько упрощены, однако они все равно уступают литеральной сериализации по простоте.

Visual Studio.NET для проекта ASP.NET Web-service по умолчанию генерирует именно стиль document/literal, а .Net Remоting придерживается, наоборот, RPC/encoded. В конечном счете, и там и там можно настроить стиль под собственные нужды, так что выбор остается за вами.

Секция binding

Здесь описывается привязка конкретных операций (Web-методов) к конкретному протоколу и стилю вызова (для SOAP это может быть document- или RPC-стиль). Так как сам WSDL является абстрактным форматом, он не имеет средств для определения зависимых от протокола параметров, его синтаксис позволяет вставлять в определенные места фрагмента binding элементы расширения для конкретного протокола. Вот фрагмент секции binding, взятый из спецификации WSDL:

<wsdl:binding name="nmtoken" type="qname"> *
  <-- extensibility element (1) --> *
  <wsdl:operation name="nmtoken"> *
    <-- extensibility element (2) --> *
    <wsdl:input name="nmtoken"? > ?
      <-- extensibility element (3) --> 
    </wsdl:input>
    <wsdl:output name="nmtoken"? > ?
      <-- extensibility element (4) --> *
    </wsdl:output>
    <wsdl:fault name="nmtoken"> *
      <-- extensibility element (5) --> *
    </wsdl:fault>
  </wsdl:operation>
</wsdl:binding>

Как видите, элементы расширения могут появляться во всех частях связывания, определяя поведение всего связывания (1), конкретной операции (2), входных (3), выходных (4) и ошибочных (5) параметров.

Рассмотрим для примера элементы расширения для протокола SOAP.

<soap:binding style="rpc|document" transport="uri"> – элемент расширения связывания, который определяет стиль SOAP-сообщения и транспорт. Расширение является обязательным.

<soap:operation soapAction="uri"? style="rpc|document"?>? – элемент расширения операции, который определяет элемент заголовка soapAction и переопределяет стиль для конкретного сообщения. Необязательное расширение.

4. и 5. <soap:body parts="nmtokens"? use="literal|encoded"? encodingStyle="uri-list"? namespace="uri"?> – элемент расширения параметров.

В нашем случае эта часть WSDL-файла будет выглядеть так:

<wsdl:binding name="SXSBinding" type="tns:SXSPort">
  <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> 
</wsdl:binding>

Здесь определяется стиль SOAP-сообщения document и транспорт http. Операции, как видите, отсутствуют.

Секция service

Эта последняя секция, которая определяет набор портов для связываний. Порт – это абстрактное понятие конечной точки, которое уточняется с помощью опять же элементов расширения. В случае SOAP элемент расширения задает адрес Web-сервиса. Вот как выглядит этот фрагмент в нашем случае:

<wsdl:service name="webserv">
  <wsdl:port name="SXSPort" binding="tns:SXSBinding">
    <soap:address location="http://dcit06/srv/webserv" /> 
  </wsdl:port>
</wsdl:service>

Более подробную информацию о WSDL 1.1, элементах расширения, вложениях и проч. можно получить по адресу http://www.w3.org/TR/wsdl.

Создание Web-методов

Давайте попробуем создать Web-метод и обратиться к SQL Server через Web. Допустим, вы настроили Web-сервис на базу Northwind. На вкладке «Virtual Names» консоли «IIS Virtual Directory Management for SQLXML 3.0» выберите из списка webserv и нажмите кнопу «Configure». Появится диалоговое окно, изображенное на рисунке 2.


Рисунок 2. Создание Web-метода.

Более всего здесь интересен раздел «Output as», который позволяет настроить формат результирующего сообщения. Вне зависимости от этих настроек формат SOAP-конверта (soap envelope) будет таким:

<?xmlversion="1.0"encoding="utf-16"?>
<SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:sqltypes="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types"
  xmlns:sqlmessage="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types/SqlMessage"
  xmlns:sqlresultstream="http://schemas.microsoft.com/SQLServer/2001/12/SOAP/types/SqlResultStream"
  xmlns:tns="uri Web-сервиса">
  <SOAP-ENV:Body>
    <tns:ИмяМетодаResponse>
      <!-- Содержимое зависит от настроек (опции Output as) -->
    </tns:ИмяМетодаResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Далее описываются каждый из типов опции «Output as» и их влияние на содержимое SOAP-конверта.

XML objects

В случае указания этот параметра возвращаемый набор рекордсетов будет представлен элементами SqlXml. После набора элементов SqlXml будет идти один элемент SqlResultCode, который описывает возвращаемый хранимой процедурой код.

Оба этих элемента – дочерние элементы ИмяМетодаResult, который имеет тип SqlResultStream.

Если хранимая процедура возвращает не только recordset, но еще и некоторые параметры, то после элемента ИмяМетодаResult будет следовать набор элементов, содержащих значения возвращаемых параметров. Пример:

<tns:ИмяМетодаResult xsi:type="sqlresultstream:SqlResultStream">
  <sqlresultstream:SqlXml>результирующий рекордсет</sqlresultstream:SqlXml>*
  <sqlresultstream:SqlResultCode xsi:type="sqltypes:SqlResultCode" sqltypes:IsNested="false">возвращаемый код</sqlresultstream:SqlResultCode>
</tns:ИмяМетодаResult>
<tns:ИмяВыходногоПараметра>значение</tns:ИмяВыходногоПараметра>*

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

<?xmlversion="1.0"encoding="utf-16"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Body>
    <SOAP-ENV:Fault>
      <faultcode>SOAP-ENV:Server</faultcode>
      <faultstring>Runtime errors.</faultstring>
    </SOAP-ENV:Fault>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Dataset objects

С помощью этой опции можно настроить SQLXML так, чтобы результирующий конверт состоял из набора фрагментов в формате DiffGram, который является одним из «родных» форматов объекта DataSet из ADO.NET. Это наиболее предпочтительная опция, если клиент написан с использованием платформы .Net Framework. DiffGram-ы записываются в отдельные элементы SqlRowSet, которые являются дочерними элементу SqlResultStream. Кроме набора DiffGram, элемент SqlResultStream также содержит элемент SqlResultCode, представляющий возвращаемый код хранимой процедуры. После элемента SqlResultStream может идти неопределенное количество элементов выходных параметров. Например:

<tns:ИмяМетодаResult xsi:type="sqlresultstream:SqlResultStream">
  <sqlresultstream:SqlRowSet>результирующий рекордсет в формате DiffGram</sqlresultstream:SqlRowSet>*
  <sqlresultstream:SqlResultCode xsi:type="sqltypes:SqlResultCode" sqltypes:IsNested="false">возвращаемый код</sqlresultstream:SqlResultCode>
</tns:ИмяМетодаResult>
<tns:ИмяВыходногоПараметра>значение</tns:ИмяВыходногоПараметра>*

Single dataset

Эта опция с точки зрения пользователя мало чем отличается от предыдущей, если только вы не работаете с несколькими результирующими наборами. Как я уже сказал выше, в случае выбора опции DataSet Object несколько рекордсетов будут представлены соответствующим количеством фрагментов SqlRowSet. Значит, на клиенте вы будете работать с несколькими наборами отдельных объектов DataSet, что не очень удобно, учитывая, что DataSet может содержать множество таблиц. При указании параметра Single dataset SQLXML будет сохранять все рекордсеты в один DiffGram. При этом необходимость в элементе SqlRowSet отпадает. Пример:

<tns:ИмяМетодаResult xsi:type="sqlresultstream:SqlResultStream">
  результирующий рекордсет в формате DiffGram
  <sqlresultstream:SqlResultCode xsi:type="sqltypes:SqlResultCode" sqltypes:IsNested="false">возвращаемый код</sqlresultstream:SqlResultCode>
</tns:ИмяМетодаResult>
<tns:ИмяВыходногоПараметра>значение</tns:ИмяВыходногоПараметра>*

Web-методы

Теперь давайте, наконец, создадим несколько Web-методов для тестирования, которое будет описано в следующем разделе. Для начала нужно создать одну хранимую процедуру с output-параметром и одну функцию (ну, не нашлось в базе данных Northwind таковых). Вот скрипт их создания:

Хранимая процедура:
        create
        proc test_output @i int out
asset @i = 10
UDF:
        create
        function test_ret_func()
returns int
asbeginreturn 1010
end

Теперь щелкните на кнопке с тремя точками (см. рисунок 2). Перед вами должно появится диалоговое окошко, изображенное на рисунке 3.


Рисунок 3. Хранимые процедуры и функции базы данных Northwind.

Выберите из списка: CustOrderHist, test_output и test_ret_func.

После нажатия кнопки ОК, в доселе пустом WSDL-файле, в разделе types схемы http://dcit06/srv/webserv должно появиться следующее:

  <xsd:element name="CustOrderHist">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element minOccurs="0" maxOccurs="1"name="CustomerID" type="xsd:string" nillable="true"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="CustOrderHistResponse">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element minOccurs="1" maxOccurs="1"name="CustOrderHistResult" type="sqlresultstream:SqlResultStream"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="test_output">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element minOccurs="0" maxOccurs="1"name="i" type="xsd:int" nillable="true"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="test_outputResponse">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element minOccurs="1" maxOccurs="1"name="test_outputResult" type="sqlresultstream:SqlResultStream"/>
        <xsd:element name="i" type="xsd:int" nillable="true"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="test_ret_func">
    <xsd:complexType>
      <xsd:sequence/>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="test_ret_funcResponse">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="returnValue" type="xsd:int" nillable="true"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>

Это описания используемых типов элементов, с помощью которых происходит формирование soap-конверта.

Описания параметров выбранных методов (CustOrderHist, test_output и test_ret_func) должны появиться в соответствующих секциях message wsdl-документа.

<wsdl:message name="CustOrderHistIn">
  <wsdl:part name="parameters" element="tns:CustOrderHist"/>
</wsdl:message>
<wsdl:message name="CustOrderHistOut">
  <wsdl:part name="parameters" element="tns:CustOrderHistResponse"/>
</wsdl:message>
<wsdl:message name="test_outputIn">
  <wsdl:part name="parameters" element="tns:test_output"/>
</wsdl:message>
<wsdl:message name="test_outputOut">
  <wsdl:part name="parameters" element="tns:test_outputResponse"/>
</wsdl:message>
<wsdl:message name="test_ret_funcIn">
  <wsdl:part name="parameters" element="tns:test_ret_func"/>
</wsdl:message>
<wsdl:message name="test_ret_funcOut">
  <wsdl:part name="parameters" element="tns:test_ret_funcResponse"/>
</wsdl:message>

И, наконец, сами методы появляются в секции portType.

<wsdl:portType name="SXSPort">
  <wsdl:operation name="CustOrderHist">
    <wsdl:input message="tns:CustOrderHistIn"/>
    <wsdl:output message="tns:CustOrderHistOut"/>
  </wsdl:operation>
  <wsdl:operation name="test_output">
    <wsdl:input message="tns:test_outputIn"/>
    <wsdl:output message="tns:test_outputOut"/>
  </wsdl:operation>
  <wsdl:operation name="test_ret_func">
    <wsdl:input message="tns:test_ret_funcIn"/>
    <wsdl:output message="tns:test_ret_funcOut"/>
  </wsdl:operation>
</wsdl:portType>

Как видите, SQLXML принял решение, что все операции будут идти по сценарию запрос/ответ, о чем свидетельствуют теги input/output каждой операции.

СОВЕТ

Возможно, вас не устроят названия секций, которые выбирает SQLXML. SXS – это, скорее всего, акроним от SQL Server Xml Support. Вы вправе дать всем элементами WSDL-файла свои собственные названия. Это совершенно не повлияет на работоспособность Web-сервиса. Правда, после следующей настройки с помощью консоли, все имена будут исправлены на стандартные.

Что ж, теперь все готово к тестированию.

Тестируем Web-сервис

SOAP-сообщение – это простой XML-документ, который состоит из двух фрагментов: необязательного фрагмента Header и обязательного Body. Оба этих фрагмента должны находиться в корневом элементе Envelope. Чтобы понять, что SOAP – это довольно просто, давайте пока формировать запросы «ручками», используя для отправки пакетов компонент XMLHTTP. Все web-методы должны возвращать xml objects (см. рисунок 2).

XMLHTTP

Создавать SOAP-сообщения очень просто, если есть WSDL-документ, и вы его четко придерживаетесь. Будем двигаться небольшими шагами. Начнем с процедуры test_output. Сначала необходимо создать компонент XMLHTTP и сформировать начало SOAP-тела (Body).

VBScript
        Dim xmlhttp
Set xmlhttp = CreateObject("Msxml2.XMLHTTP")
xmlhttp.open "POST","http://dcit06/srv/webserv",falseDim soap_mes
soap_mes = "<?xml version=""1.0"" encoding=""windows-1251""?>" _
  & "<soap:Envelope xmlns:soap=" _
  &        ""http://schemas.xmlsoap.org/soap/envelope/"">" _
  & "<soap:Body>" _
  & ...

Из WSDL-файла видно, что операция test_output состоит из запроса и ответа. Запрос определяется набором параметров test_outputIn (секция message под названием test_outputIn). Этот набор состоит из одного элемента test_output, в который вложен элемент i, представляющий собственно значение выходного параметра для метода., Тип элемента i - xsd:int, он может принимать значение nil. Дабы не привлекать пространство имен http://www.w3.org/2001/XMLSchema-instance, в котором определен атрибут nil, задающий пустое значение, просто передадим в качестве значения параметра i нуль.

Остаток скрипта
        "<test_output xmlns=""http://dcit06/srv/webserv"">" & _
        "<i>0</i>" & _
      "</test_output>" & _
    "</soap:Body>" & _
  "</soap:Envelope>"
xmlhttp.send soap_mes
WScript.Echo "****************************************"
WScript.Echo vbTab & "Response for test_output"
WScript.Echo "----------------------------------------"
WScript.Echo xmlhttp.responseText

После формирования строки запроса можно вызывать Web-сервис и вывести результаты на консоль. Подобным же образом составляется тест для хранимой процедуры CustOrderHist и функции test_ret_func.

ПРЕДУПРЕЖДЕНИЕ

Мною замечена ошибка в SQLXML: параметры Web-метода трактуются как неквалифицированные элементы (unqualified elements), т.е. если написать запрос так:

<t:test_output xmlns:t="http://dcit06/srv/webserv">

<t:i>0</t:i>

</t:test_output>
, то SQLXML вернет ошибку: The input parameter &apos;t:i&apos; is not a parameter to the stored procedure called. Если убрать префикс пространства имен у элемента i (как если бы он к нему не относился)– все проходит без ошибок.

Наверняка через некоторое (не слишком большое) время вам надоест создавать SOAP-сообщения вручную. Раз так, переходим к следующему разделу.

ПРИМЕЧАНИЕ

Прекрасным инструментом для отладки Web-сервисов является XMLSPY. Он автоматически на основе WSDL-файла создает сообщения SOAP (которые можно затем вручную отредактировать), позволяет вызвать Web-метод, а также может выступать в роли трассировщика, позволяя отлаживать Web-сервис.

SOAP Toolkit

Если вы никогда не работали с SOAP Toolkit, очень рекомендую замечательную статью Ивана Андреева «Использование протокола SOAP в распределенных приложениях».

SOAP Toolkit возвращает ответ в виде объекта типа IXMLDOMNodeList. Это коллекция элементов IXMLDOMNode, у которых есть свойство xml. Его-то мы и будем использовать для вывода результатов на консоль. В коде присутствует большое количество комментариев, поэтому больше ничего говорить не буду, смотрите сами:

Вызов функции test_output с помощью SOAP Toolkit:
        'Создание высокоуровнего объекта SoapClient30, реализующего динамический IDispatch
        'на основе информации из WSDL-файла
        Dim SoapClient
Set SoapClient = CreateObject("MSSOAP.SoapClient30")

'Здесь происходит чтение WSDL, анализ и генерация IDispatch,'который будет содержать все, указаные в WSDL операции'В нашем случае это test_output, test_ret_func и CustOrderHist
SoapClient.MSSoapInit "http://dcit06/srv/webserv?wsdl"Dim ReturnNodeList
Dim ret_val
'Вызываем Web-метод test_output. Возвращаемое значение сохраняется'в переменной ret_val'Часть необработанного SOAP-ответа возвращается в виде IXMLDOMNodeListSet ReturnNodeList = SoapClient.test_output(ret_val)

'Вывод результатов на консоль
WScript.Echo "****************************************"
WScript.Echo vbTab & "Response for test_output"
WScript.Echo "----------------------------------------"'Перебираем элементы и выводим их на консольForEach Node In ReturnNodeList
    WScript.Echo Node.xml
Next'Вывод на консоль возвращаемого значения
WScript.Echo "Return value is " & ret_val

.NET Framework

Уж с чем-чем, а с поддержкой Web-сервисов в .NET Framework все в порядке. По сравнению с SOAP Toolkit, здесь все гораздо проще и легче. Чтобы начать работу, нужно создать простое консольное приложение и добавить Web-ссылку (web reference) на Web-сервис.

Примеры вызовов Web-методов тривиальны, поэтому стоит упомянуть лишь передачу ответов в формате DiffGram. Для того чтобы это стало возможным, зайдите в консоль администрирования и задайте для процедуры CustOrderHist значение Output as DataSet objects. Ниже приводится полный пример VB.NET-программы получения результатов Web-метода как объекта DataSet.

Автоматическое создание объекта DataSet путем вызова метода ReadXml в режиме DiffGram
        Module Module1

    Sub Main()
        Dim prx As dcit06.procedures1
        Try
            prx = New dcit06.procedures1
            ' В момент выхове метода CustOrderHist происходит следующее'   1. Вызывается soap-метод CustOrderHist'   2. xml-фрагмент в формате DiffGram извлекается из ответа'   3. Происходит создание DataSet и вызов метода ReadXmlDim response() AsObject = prx.CustOrderHist("BLAUS")
            ' response(0) – готовый объект DataSet' response(1) – код возврата хранимой процедурыDim ds As DataSet = response(0)
        Finally
            prx.Dispose()
        EndTryEndSubEndModule

Надо сказать, что работать с Web-сервисами из .NET Framework значительно легче, чем через Soap Toolkit. Производительность, как минимум, не хуже, а наглядность и удобство на порядок выше.

Поддержка SQLXML в .NET Framework

Через ADO.NET нельзя получить доступ к таким сервисным провайдерам, как SQLXMLOLEDB, поэтому комплект SQLXML 3.0 содержит сборку Microsoft.Data.SqlXml. В нее входят следующие классы:

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

Свойства класса SqlXmlCommand

В качестве типа команды можно использовать следующие значения.

Тип Описание
SqlXmlCommandType.Sql Стандартный код T-SQL.
SqlXmlCommandType.XPath Запрос XPath.
SqlXmlCommandType.Template Выполнение шаблона.
SqlXmlCommandType.TemplateFile Выполнение шаблона, находящегося в файле.
SqlXmlCommandType.UpdateGram Выполнение апдейтаграммы. Подробности в [1]
SqlXmlCommandType.Diffgram Выполнение дифграммы.

В форумах по ASP.NET иногда поднимаются вопросы наподобие: «Как наиболее эффективно и просто передать XML-документ пользователю?». Вот пример кода на VB.NET:

Передача XML пользователю с помощью SqlXmlCommand.
      Dim cmd as SqlXmlCommand
cmd = New SqlXmlCommand("Provider=SQLOLEDB;User ID=user;" _
  & "Data Source=server;Password=password;")
cmd.CommandText = "select * from [table] for xml auto"
cmd.CommandType = SqlXmlCommandType.Sql
cmd.RootTag = "root"
Response.Clear()
Response.ContentType = "text/xml"
cmd.ExecuteToStream(Response.OutputStream)
Response.End()

Помимо метода ExecuteToStream, класс SqlXmlCommand предоставляет другие удобные методы выборки данных:

Примеры выполнения запросов XPath и шаблонов можно посмотреть в [1]. Более подробную информацию об использовании этих классов можно получить в [4].

Тестовый проект

Давайте создадим небольшое тестовое Intranet-приложение, которое закрепит наши знания в этой области. Я предлагаю создать простенький форум, так как он позволит нам продемонстрировать наиболее характерные особенности SQLXML.

Цели и ограничения

Форум должен быть создан без единой строчки серверного кода, т.е. кода на ASP или ASP.NET. Вся серверная логика должна реализовываться с помощью хранимых процедур, которые должны быть доступны клиенту как Web-методы. Данные клиенту должны передаваться в формате XML с помощью шаблонов SQLXML. Преобразование XML в HTML будет производиться на основе XSLT-схемы, также на клиенте. Форум должен поддерживать персонализацию, т.е. возможность регистрироваться и создавать авторские сообщения. Сообщения может просматривать любой посетитель, но для создания нового сообщения необходима учетная запись. Сама страничка сообщений должна поддерживать разбивку на страницы. При этом пользователь может сам задавать размер страницы, т.е. количество сообщений на ней.

В силу того, что никакие серверные технологии не используются, поддержка сессий и приложений отсутствует.

Дизайн

Начнем с проектирования структуры базы данных. В приложении будут участвовать объекты: пользователи, роли пользователей и сообщения. Связь между пользователями и ролями – «многие ко многим», а между сообщениями и пользователями – «многие к одному». К атрибутам пользователя относятся: имя, полное имя, дата регистрации и пароль. Имя пользователя и пароль должны быть уникальными. Кроме этого, ни пароль, ни имя не должны допускать пустых строк. К атрибутам сообщения относятся: дата создания сообщения, тело сообщения и заголовок сообщения. Диаграмма таблиц представлена на рисунке 4.


Рисунок 4. Диаграмма таблиц БД.

Для работы с таблицами необходимы хранимые процедуры, которые будут доступны как Web-методы. Их, как и таблиц, немного:

Полный скрипт создания БД SForum, всех таблиц, хранимых процедур и пользователя (SForumUser), учетная запись которого будет использоваться для доступа к базе, можно найти среди прилагающихся к статье файлов.

Наше Web-приложение будет состоять из нескольких файлов:

Диаграмма сайта приведена на рисунке 5.


Рисунок 5. Диаграмма сайта.

Сообщения будут передаваться на страницу board.htm в формате xml, с помощью шаблона board.xml. Если вы не знакомы с шаблонами SQLXML, прочитайте соответствующий раздел первой части статьи.

Формат XML-документа, представляющего сообщения:
<board>
    <info .../>
    <message posted="дата создания сообщения" author="автор сообщения" title="заголовок" id="идентификатор сообщения">
        тело сообщения
    </message>
</board>

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

Board.xml
<?xmlversion="1.0"encoding="windows-1251" ?> 
<board xmlns:sql="urn:schemas-microsoft-com:xml-sql">
    <sql:header>
        <sql:param name="Page">0</sql:param>    
        <sql:param name="PageSize">10</sql:param>    
    </sql:header>
    <sql:query>
        exec GetMessages @Page,@PageSize
    </sql:query>
    <sql:query>
        select 1 as tag,0 as parent,
            (count(*)-1)/@PageSize + 1 as 'info!1!pages'
          from Messages
          for xml explicit
    </sql:query>
</board>

Страницы отсчитываются с нуля, а количество сообщений на странице по умолчанию равно десяти. Для генерации сообщений я использовал дополнительную хранимую процедуру GetMessages.

Реализация

Итак, после создания БД, таблиц и хранимых процедур настала пора настроить виртуальные директории с помощью консоли «IIS Virtual Directory Management for SQLXML 3.0». Зайдите в свойства виртуальной директории srv, на вкладку Security. Необходимо поменять Credentials, под которыми SQLXML будет обращаться к базе данных. В поле User Name введите значение SForumUser, в поле Password – simple. На вкладке Data Source выберите «Use default database for current login». На вкладке Settings выберите опцию «Allow templates».

Теперь необходимо на вкладке Virtual Names настроить виртуальное имя для шаблонов. Я не буду объяснять, как это делается, вся информация содержится в [1].

Следующим шагом будет создание трех Web-методов для виртуального имени webserv: AddUser, AddMessage и IsUserRegistered. Как это сделать, подробно обсуждалось в разделе «Создание Web-методов».

Для создания самого Web-приложения откройте в VS.NET 2003 пункт «New Project», выберете тип проекта Empty Web Project (не важно для какого языка) и введите имя SForum.

Board.htm

Начну с создания главной странички сайта – board.htm. Я не буду приводить здесь ее целиком, выделю лишь ключевые моменты. Для скачивания файла board.xml и board.xslt используются XML-острова (XML islands).

<xml src="http://dcit06/srv/template/board.xml" id="MesSrc"></xml>
<xml src="board.xslt" id="xslt"></xml>

Сами сообщения будут находиться в теге div.

<div id="messages"/>

Наполнить его содержанием очень просто. Вот код, который выполняется в момент загрузки страницы:

messages.innerHTML = MesSrc.XMLDocument.transformNode(xslt.XMLDocument);

Для обновления странички сообщений используется несколько более сложный код.

messages.innerHTML = "Подождите..."; 
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); 
xmlhttp.open("GET", MesSrc.src + "?Page=" + curPage + "&PageSize=" _
  + mes_per_page.value, false);
xmlhttp.send();
MesSrc.XMLDocument = xmlhttp.responseXML;
messages.innerHTML = MesSrc.XMLDocument.transformNode(xslt.XMLDocument);

Сообщения скачиваются с помощью компонента XMLHTTP, при этом в строке запроса передаются следующие параметры: номер запрашиваемой страницы и размер страницы в сообщениях. Первый параметр хранится на страничке как переменная curPage, а второй представлен полем ввода:

<input type="text" id="mes_per_page">

Если все сделано правильно, страничка должна выглядеть как на рисунке 6.


Рисунок 6. Страничка board.htm.

Перейдем к следующим страницам.

Login.htm

Обращаться к Web-сервису из браузера мы будем с помощью «поведения Web-сервиса» (Webservice behavior). «Поведение Web-сервиса» – это отдельная очень обширная и сложная тема, которая никак не входит в рамки данной статьи. Если вы не знакомы с этой технологией или имеете смутное представление о методах ее реализации, отправляю вас к MSDN.

«Поведение Web-сервиса» позволяет вызывать Web-методы на клиенте, в коде браузера. Организовав доступ к хранимым процедурам SQL Server-а через Web-методы, можно полностью избавиться от необходимости написания какого-либо серверного кода.

Код «поведения Web-сервиса» находится в файле webservice.htc, который можно скачать по адресу http://msdn.microsoft.com/downloads/samples/internet/behaviors/library/webservice/default.asp. Связать «поведение Web-сервиса» с каким-либо элементом странички можно статически или динамически.

Статическое связывание «поведения Web-сервиса»:
<body>
    <div id="service" style="behavior:url(webservice.htc)"></div>
</body>

После того как «поведение Web-сервиса» назначено элементу, вы должны связать его с Web-сервисом, при помощи метода useService. Обычно это делается в обработчике onload окна, однако вы вправе связать «поведение Web-сервиса» с Web-сервисом в любой удобный момент.

Связывание «поведения Web-сервиса» с Web-сервисом в обработчике onload:
<script language="JavaScript">
    function init()
    {
        service.useService("URL","Friendly name");
    }
</script>
<body onload="init()">
    <div id="service" style="behavior:url(webservice.htc)"></div>
</body>

С одним «поведением Web-сервиса» можно связать несколько Web-сервисов, главное чтобы их имена, передаваемые во втором параметре, несовпадали.

После этого можно вызывать Web-методы с помощью функции callService. Но прежде чем рассмотреть, как это делается, хочется остановиться вот на чем. Как вы понимаете, вызов любого Web-метода может занять продолжительное время и будет очень плохо, если приложение пользователя, в данном случае – браузер, на это время будет заблокировано. По умолчанию «поведение Web-сервиса» вызывает Web-методы асинхронно. Это значит, что результаты вызова не доступны вызывающей стороне сразу же после того, как метод callService вернул управление. «Поведение Web-сервиса» определяет набор событий, среди которых есть событие onResult, вызывающееся после получения результатов от Web-метода. Так как с одним «поведением Web-сервиса» может быть связано несколько Web-сервисов, а пользователь может одновременно вызывать несколько методов, для идентификации результатов вызова Web-метода вводится специальный счетчик. Он возвращается функцией callService и, как правило, сохраняется в глобальной переменной. В качестве счетчика в примере, приведенном ниже, используется глобальная переменная callid.

Вызов Web-метода
<SCRIPT language="JavaScript">
var callid = 0;
function init()
{
    service.useService("URL","MyService");
    callid = service.MyService.callService("method");
}
function onWSresult()
{
    // Если в процессе вызова возникла ошибка, и это "наш" методif((event.result.error) && (callid == event.result.id))
    {
        //event.result.errorDetail.code;//event.result.errorDetail.string;//event.result.errorDetail.raw;
    }
    // Если ошибки нет, и это "наш" методelseif((!event.result.error) && (callid == event.result.id))
    {
        alert(event.result.value);
    }
    // Произошло что-то странноеelse
    {
        alert("Something else fired the event!");
    }
}
</SCRIPT>
<body onload="init()">
    <div id="service" style="behavior:url(webservice.htc)" onresult="onWSresult()"></div>
</body>

В данном примере, функция callService принимает один параметр – имя Web-метода, однако, вы также можете указывать неопределенное количество параметров, если это требуется. Например, для Web-метода add необходимо два параметра:

callid = service.MyService.callService("add", 1, 10);

Как видите, в использовании «поведения Web-сервиса» нет ничего сложного.

«Поведение Web-сервиса» обладает большим количеством свойств, и рассмотреть их всех затруднительно, с их помощью вы можете указывать:

Но вернемся к нашим, как говорится, баранам. Имеется страничка Login.htm, которая содержит поля ввода для имени пользователя и пароля. Необходимо проверить, действительно ли данный пользователь зарегистрирован на сайте. Это делается с помощью Web-метода IsUserRegistered.

Проверка учетной записи пользователя:
          function onSubmit() 
{ 
  if (!validateForm())
  {
    ...
  } 
  else
  { 
    service.Simplewebserv.callService("IsUserRegistered",
      NickName.value, psw.value); 
  }
}

Функция onSubmit вызывается при нажатии на кнопку «Вход». Она проверяет корректность введенных пользователем значений и вызывает Web-метод IsUserRegistered с параметрами: имя пользователя и пароль.

Результат анализируется в обработчике события onResult:
          function onWSresult() 
{ 
    if (!event.result.error){
        var doc = new ActiveXObject("MSXML2.DOMDocument.3.0"); 
        doc.loadXML(event.result.raw.xml); 
        if (doc.parseError.errorCode == 0){ 
            var UserIdNode = doc.selectSingleNode("//sqlresultstream:SqlResultCode"); 
            if (UserIdNode.text == 0){ 
                gen_error.innerText = "Неверное имя пользователя или пароль."; 
                InvalidPsw(); InvalidNickName(); 
            } 
            else{ 
                window.returnValue = "NickName:" + NickName.value + ";UserId:" + UserIdNode.text + ";"; 
                window.close(); 
            }
        } 
        else{ 
            gen_error.innerText = "Возникла ошибка: " + doc.parseError.reason; 
        }
    } 
    else{ 
        gen_error.innerText = "Возникла ошибка: " + event.result.errorDetail.string; 
    }
}

Так как SQLXML не использует кодирование RPC, нельзя воспользоваться свойством value объекта result. Вместо этого придется «вручную» проанализировать ответ Web-метода в формате xml. Удобнее всего это сделать с помощью объектной модели документа XmlDOM. Интересующее нас значение, т.е. результат вызова находится в элементе SqlResultCode (см. «XML objects»).

Register.htm

С помощью этой формы производится регистрация пользователя. На ней расположены поля ввода для имени пользователя, пароля и полного имени пользователя (FullName). Для регистрации вызывается метод AddUser.

service.Simplewebserv.callService("AddUser", NickName.value, psw.value,
  FullName.value);

В общем, ничего сложного здесь нет. Результат обрабатывается тем же самым образом, что и на страничке login.htm.

Post.htm

Web-метод AddMessage вызывается и обрабатывается схожим образом.

Заключение

Когда следует использовать описанный в статье подход? Очевидно, что для слишком больших систем он неприемлем, так как:

Этот список недостатков решения. Наоборот, для простых систем, вроде корпоративных домашних страничек, форумов и проч. такое решение соответствует как нельзя лучше.

Microsoft продолжает развивать данный подход: в новой версии SQL Server под кодовым названием «Yukon» будет встроенная поддержка Internet-сервера, а ISAPI-расширение sqlxml.dll будет входить в дистрибутив.

Литература


Эта статья опубликована в журнале RSDN Magazine #1-2004. Информацию о журнале можно найти здесь
    Сообщений 0    Оценка 115        Оценить