Введение
В статье Тима Эдвардса (Tim Ewald) Accessing Raw SOAP
Messages in ASP.NET Web Services
(http://msdn.microsoft.com/msdnmag/issues/03/03/WebServices/),
опубликованной в мартовском номере MSDN Magazine, автор описывает
интересный способ управления потоком SOAP-сообщений, непосредственно используя
SOAP Extension. Это может быть выгодным основанным на концепции потоков способом
организации доступа к необработанным SOAP-сообщениям, который к тому же
обеспечивает вам полный контроль над процессом синтаксического разбора
сообщения. Но Тим также ссылается на более простой способ получения некоторых
аналогичных преимуществ, но с условием, что данные, к которым вы организовываете
доступ, должны быть один раз разобраны внутренним обработчиком ASMX. Простой
способ заключается в использовании параметра XmlElement для
вашего Web-метода. В разделе At Your Service за этот месяц я рассматриваю
различные пути, в которых применение параметра XmlElement к
вашему Web-сервису может быть выигрышным механизмом организации доступа к
необработанным XML-данным, и то, как вы можете получить высокий уровень контроля
над вашим Web-сервисом посредством этого механизма.
Сериализация Web-метода
Перед тем, как мы углубимся в детали того, как манипулировать Web-методами,
используя параметры XmlElement, давайте взглянем на то, как
Web-методы используют XmlSerializer в .NET Framework для вызова методов и
создания ответа. На рис. 1 схематически показано, что происходит, когда приходит
XML-тело SOAP-сообщения.
Рис. 1. Роль XmlSerializer в стандартном вызове Web-метода
ASP.NET
XML передается в XmlSerializer для десериализации в экземпляры классов в
управляемом коде, которые преобразовываются во входные параметры для Web-метода.
Аналогичным образом, выходные параметры и возвращаемые значения Web-метода
сериализуются в XML для создания тела ответного SOAP-сообщения.
Если мы создаем Web-метод, который суммирует два целых (integer) вместе и
возвращает результат, тогда управляемый код может выглядеть примерно так:
[WebMethod]
public int Add (int x, int y)
{
return x + y;
}
SOAP-сообщение, которое может быть отправлено в этот Web-метод, должно
выглядеть примерно так:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Add
xmlns="http://msdn.microsoft.com/AYS/XmlElementService">
<x>3</x>
<y>2</y>
</Add>
</soap:Body>
</soap:Envelope>
Итак, XmlSerializer используется для преобразования XML, выделенного красным,
в параметры для расположенного выше метода Add. Поскольку целые
(integers) являются типами, это довольно просто, но теперь мы рассмотрим более
сложный пример.
Предположим, у нас есть Web-метод, который принимает не тип данных.
Рассмотрим следующий код на C#:
public class MyClass
{
public string child1;
public string child2;
}
[WebMethod]
public void SubmitClass(MyClass input)
{
//Что-нибудь делаем с состовным входным параметром
return;
}
Этот метод принимает только один параметр, но этот параметр не из тех,
которые преобразовываются в простой XML-тип. Кстати, он преобразовывается в
составной тип, который представлен входным элементом в SOAP-конверте,
приведенном ниже:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<SubmitClass
xmlns="http://msdn.microsoft.com/AYS/XEService">
<input>
<child1>foo</child1>
<child2>bar</child2>
</input>
</SubmitClass>
</soap:Body>
</soap:Envelope>
XmlSerializer принимает элемент input и десериализует его в
экземпляр типа MyClass, который был описан в коде для этого
Web-метода перед вызовом метода.
Еще один элемент поддержки Web-сервиса в .NET Framework, о которой вы должны
знать — это простота разработки кода-потребителя Web-сервиса. Причина, по
которой это возможно, в том, что для вашего Web-метода автоматически создается
WSDL, который полностью описывает детали интерфейса вашего Web-сервиса. Если вы
посмотрите на WSDL, сгенерированный ASP.NET для этого метода, вы заметите, что
элемент input был определен в секции types,
как показано ниже:
<s:complexType name="MyClass">
<s:sequence>
<s:element minOccurs="0" maxOccurs="1"
name="child1" type="s:string" />
<s:element minOccurs="0" maxOccurs="1"
name="child2" type="s:string" />
</s:sequence>
</s:complexType>
Поддержка Add Web Reference в Visual Studio® .NET определит
на клиенте класс, который соответствует предыдущему описанию класса
MyClass, так, что ваш вызывающий код может вызывать Web-метод
как функцию. Вызов метода приведен ниже:
localhost.XEService proxy = new localhost.XEService();
localhost.MyClass myInstance = new localhost.MyClass();
myInstance.child1 = "foo";
myInstance.child2 = "bar";
proxy.SubmitClass(myInstance);
XmlSerializer также используется на клиенте для сериализации экземпляра
класса в XML, который соответствует описанной схеме из WSDL.
Отказ от использования XmlSerializer в Web-методах
XmlSerializer довольно полезен и силен, когда дело касается написания и
потребления Web-сервисов. Он устанавливается взаимоотношения между передаваемым
XML и классами в вашем прозрачном управляемом коде. Зачастую, это очень полезная
штука и основная причина, почему Web-методы ASP.NET являются одним из самых
эффективных способов создания Web-сервисов.
Но что, если мне не нравится то, как работает XmlSerializer? Что если я хочу
иметь больший контроль над процессами сериализации и десериализации? Возможно,
мне не нравится тот факт, что XmlSerializer не проверяет полученный XML на
соответствие схеме сообщения. Возможно, я хочу выборочно решать, какие части
сообщения я хочу десериализовать. Может быть я даже не знаю, как выглядит схема
для поступающего сообщения. Существует ли простой способ, с помощью которого я
могу обойти стандартное использование XmlSerializer и самостоятельно управлять
процессом десериализации сообщения? Есть два варианта решения этой проблемы: мы
сосредоточим внимание на использовании параметров
XmlElement.
Сила XmlElement
В двух предыдущих примерах мы видели, как два параметра и составной класс
сериализуются в параметры Web-метода. Но что происходит, если сами параметры
являются XML?
Рассмотрим следующий Web-метод:
[WebMethod]
public void SubmitXmlElement(XmlElement input)
{
// Что-то делаем с входным XML
return;
}
В этом примере Web-метод в качестве входных данных принимает объект
System.Xml.XmlElement.
Следующий SOAP-конверт может использоваться для отправки сообщения этому
Web-сервису.
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<SubmitXmlElement
xmlns="http://msdn.microsoft.com/AYS/XEService">
<input>
<MyClass xmlns="">
<child1>foo</child1>
<child2>bar</child2>
</MyClass>
</input>
</SubmitXmlElement>
</soap:Body>
</soap:Envelope>
Обратите внимание, что я посылаю что-то очень близкое к тому, что мы
отправляли в предыдущий Web-метод. Ключевое отличие, однако, в том, что
вследствие описания Web-метода XmlSerializer десериализует параметр в объект
XmlElement, а не в объект MyClass.
Действительность в том, что XmlSerializer десериализует весь SOAP-конверт. Когда
он определяет, что параметр для Web-метода типа XmlElement,
процесс десериализации становится тривиальным, и соответствующая часть Infoset
предоставляется как XmlElement.
Обратная сторона этого метода в том, что автоматический WSDL, определенный
для этого метода, теперь не включает информацию о структуре параметра
input. В итоге, Web-метод не определяет структуры для параметра
input. Это может быть и хорошо, и плохо.
Когда для этого типа метода в Visual Studio .NET вы используете Add
Web Reference, параметр input для прокси-класса
определяется как XmlNode (от которого происходит
XmlElement). Следовательно, мы могли бы послать любой XML в тот
метод. Это имеет смысл, потому что, если вы посмотрите на WSDL, то увидите, что
элемент input определен как составной тип, тип единственного
элемента которого xsd:any. Существуют определенные бизнес
ситуации, когда необработанный, не имеющий схемы XML действительно хорош для
отправки Web-сервису, но часто это не так. Чаще наш Web-сервис будет ожидать
данные, отвечающие одному или более известным форматам.
Итак, если я знаю формат моих параметров, почему мне может когда-либо
захотеться использовать XmlElement? Потому что теперь вы можете
самостоятельно управлять процессом десериализации, включая даже возможность не
проведения десериализации. Можно улучшить производительность, и вы имеет
возможность осуществить такие вещи как трансформация XML, проверка достоверности
XML или осуществление десериализации, основанной на содержимом.
Другая часть этой проблемы в том, как вы определите ваш WSDL, чтобы он
оповещал о том, что может обрабатывать несколько известных типов параметров.
Здесь мы не будем углубляться в этот вопрос, но познакомьтесь с советами по
написанию Web-методов, которые предоставляют несколько типов параметров, и с
тем, какое влияние это оказывает на сгенерированный WSDL, в разделе Versioning Options
(http://msdn.microsoft.com/webservices/building/xmldevelopment/xmlserialization/default.aspx?pull=/library/en-us/dnservice/html/service10152002.asp).
XmlElement и проверка сообщений
XmlSerializer не проверяет сериализуемый им XML. Оказывается, что обычно это
не может быть большой проблемой. Например, если кто-то посылает следующий
XML:
<MyClass>
<param2>foo</param2>
<param1>bar</param1>
</MyClass>
На основании схемы, определенной ранее для вашего класса, он не должен пройти
проверку. Проблема в данном случае в том, что тип был определен как
последовательность элементов, а это означает, что их порядок должен учитываться.
Конечно же, мы не писали в нашем коде ничего такого, что свидетельствовало бы о
том, что мы действительно заботимся о порядке расположения параметров, поэтому
все может быть хорошо, если присланный нам XML не будет тщательно проверяться.
Проверка XML может быть относительно дорогим процессом, вот, вероятно, почему он
не осуществляется по умолчанию.
Однако возможны случаи, когда важна строгая проверка достоверности сообщений.
Например, если вы осуществляете запросы XPath к поступающему XML, предположения
о структуре XML могут привести к совершенно неожиданным результатам.
Итак, мы можем использовать XmlElement, чтобы получить
возможность проверять достоверность поступающего XML до того, как десериализуем
его в экземпляр класса. Рассмотрим следующий Web-метод:
[WebMethod]
public void SubmitValidatedTypedXml([XmlAnyElement]XmlElement input)
{
XmlSchemaCollection schemaCollection = new XmlSchemaCollection();
schemaCollection.Add("http://localhost/XEService/MyNewClass.xsd",
"http://localhost/XEService/MyNewClass.xsd");
// XmlValidatingReader принимает только XmlTextReader в качестве входного параметра
XmlValidatingReader validator
= new XmlValidatingReader(input.OuterXml,
XmlNodeType.Element,
null);
validator.Schemas.Add(schemaCollection);
XmlSerializer serializer
= new XmlSerializer(typeof(MyNewClassType));
MyNewClassType newInstance
= (MyNewClassType)serializer.Deserialize(validator);
// Что-то делаем с объектом MyNewClassType
}
Этот Web-метод делает несколько вещей. Его цель — принять
XmlElement, переданный в Web-метод, и вручную десериализовать
его как экземпляр класса MyNewClassType. Чтобы выполнить эту
часть процесса, создайте экземпляр класса XmlSerializer,
укажите тип, используемый как параметр конструктора, и затем вызовите метод
Deserialize.
Но в этом случае мы делаем еще кое-что. Метод Deserialize
принимает объект XmlReader в качестве входных данных. Мы могли
бы создать экземпляр класса XmlNodeReader (унаследованного от
XmlReader) и передать его в метод Deserialize.
Однако в этом случае в метод Deserialize мы передаем экземпляр
класса XmlValidatingReader. В .NET Framework проверку
достоверности XML вы проводите с помощью XmlValidatingReader.
Мы создавали экземпляр XmlValidatingReader путем передачи части
XML из нашего параметра input. Чтобы проверить XML на
соответствие схеме, средству проверки достоверности надо загрузить схему, чтобы
он знал, с чем сравнивать. Завершаем все это добавлением известной схемы в набор
схем.
Окончательная проверка достоверности XML происходит, когда вызывается метод
Deserialize объекта XmlSerializer. Это
происходит потому, что XmlValidatingReader также происходит от
XmlReader, и проверка содержащегося XML происходит по мере
того, как с помощью интерфейса XmlReader считываются различные
узлы. Эти узлы считываются, когда метод Deserialize перебирает
все узлы для создания экземпляра класса MyNewClassType.
Класс MyNewClassType очень похож на созданный нами ранее
класс MyClass, за исключением того, что я создал его путем
определения XML-схемы, используя поддержку Visual Studio для создания
XSD-файлов, и затем воспользовался утилитой XSD.exe для создания управляемого
класса с помощью следующей командной строки:
Таким образом, я получил XSD-схему для передачи в
XmlValidatingReader и код класса, в который десериализовывать
XML.
Есть еще одна важная вещь, которая включена в этот Web-метод и не входила в
предыдущую версию. В этом методе мы украшаем параметр input
параметром XmlAnyElement. Это намек на то, что этот параметр
будет десериализован из элемента xsd:any. Поскольку атрибуты не
передают никаких параметров, это означает, что весь XML-элемент для этого
параметра в SOAP-сообщении будет находиться в Infoset, который представлен этим
параметром XmlElement. Вот это и есть тонкое отличие от
предыдущего Web-метода. давайте рассмотрим, в чем состоит это отличие.
Атрибут XmlAnyElement
Чтобы понять, как работает атрибут XmlAnyElement, давайте
рассмотрим два Web-метода:
// Простой Web-метод, использующий параметр XmlElement
[WebMethod]
public void SubmitXml(XmlElement input)
{return;}
// Простой Web-метод... использующий атрибут XmlAnyElement
[WebMethod]
public void SubmitXmlAny([XmlAnyElement] XmlElement input)
{return;}
Разница между этими двумя методами в том, что для первого,
SubmitXml, XmlSerializer будет ожидать элемент
input, который должен стать прямым потомком элемента
SubmitXml в SOAP-теле. Второй метод,
SubmitXmlAny, не будет заботиться об имени потомка элемента
SubmitXmlAny. Он вставит любой XML, входящий в параметр
input. Стиль сообщения для этих двух методов из Справочной
системы ASP.NET приведен ниже. Сначала мы рассмотрим сообщение для метода без
атрибута XmlAnyElement.
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SubmitXml xmlns="http://msdn.microsoft.com/AYS/XEService">
<input>xml</input>
</SubmitXml>
</soap:Body>
</soap:Envelope>
Теперь мы посмотрим на сообщение для метода, который использует атрибут
XmlAnyElement.
<!-- SOAP message for method using XmlAnyElement -- >
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SubmitXmlAny xmlns="http://msdn.microsoft.com/AYS/XEService">
Xml
</SubmitXmlAny>
</soap:Body>
</soap:Envelope>
У метода, украшенного атрибутом XmlAnyElement, на один
элемент-оболочку меньше. Только элемент с именем метода заключает в оболочку то,
что передается в параметр input.
Если вы хотите управлять процессом десериализации XmlSerializer для вашего
Web-метода, XmlAnyElement — это ловкий прием, который можно
использовать для обработки большей части тела с помощью вашей специальной
логики. Но можем ли мы добиться передачи в экземпляр XmlElement
даже еще большей части тела? Фактически, можем, путем использования свойства
ParameterStyle атрибута
SoapDocumentMethod.
[WebMethod]
[SoapDocumentMethod(ParameterStyle = SoapParameterStyle.Bare)]
public void SubmitBody([XmlAnyElement]XmlElement input)
{
return;
}
После присваивания свойству ParameterStyle значения
SoapParameterStyle.Bare и применения
XmlAnyElement, стиль сообщения становится следующим:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>xml</soap:Body>
</soap:Envelope>
Теперь все содержимое SOAP-тела будет передано нам в наш параметр input.
Приведенный выше механизм может быть очень полезен во многих случаях, но мы
должны знать, что SOAP-тело не является, собственно, XML-документом. В
частности, не обязательно, чтобы у него был единственный корневой элемент. Как
оказалось, WS-I Basic Profile указывает на то, что у Body
должен быть один дочерний элемент; однако это не гарантируется. Если мы
действительно хотим получить доступ к необработанному XML, переданному в наш
Web-метод, нам надо принимать во внимание ситуацию, в которой тело может иметь
несколько дочерних элементов, как показано ниже.
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<name>Fred</name>
<name>Wilma</name>
<name>Betty</name>
<name>Barney</name>
</soap:Body>
</soap:Envelope>
Как оказалось, метод SubmitBody может принять такого рода
сообщение, просто нет возможности организовать доступ ко всем данным. Когда
сообщение будет десериализовано, вы сможете получить доступ только к последнему
элементу в списке параметра input.
Справиться с такой ситуацией мы можем, меняя наш одиночный
XmlElement на массив XmlElements. Следующий
метод сможет прочитать все тело присланного ему SOAP-сообщения.
[WebMethod]
[SoapDocumentMethodAttribute(ParameterStyle = SoapParameterStyle.Bare)]
public void SubmitAnything([XmlAnyElement]XmlElement [] inputs)
{
return;
}
Чем больше контроля... тем труднее код
Мы выяснили, как организовывать доступ ко всему содержимому SOAP-тела из
ASP.NET Web-метода прямо в XML. Это дает вам возможность реализовать многие
вещи, например, проверку XML на соответствие схеме перед его десериализацией,
уклонение от десериализации в первую очередь, анализ XML для определения, как вы
хотите его десериализовать, или использование многих мощных XML API, применяемых
для непосредственной работы с XML. Это также предоставляет вам возможность
управления обработкой ошибок по-своему, вместо того чтобы использовать ошибки,
которые предусмотрены для генерирования XmlSerializer.
Для тех из вас, кто хочет получить доступ к XML, посланным в ваши Web-методы,
на более низком уровне, использование параметров XmlElement
вместе с некоторыми приемами присваивания атрибутов Web-методам, описанными в
этой статье, может обеспечить вам больше контроля в Web-сервисе. Но при этом
надо написать большее количество кода и хорошо изучить основные XML-технологии,
доступные в .NET Framework. Но прелесть .NET Framework в том, что она
обеспечивает гибкость обширного контроля на низком уровне и, в то же время,
предоставляет простую и эффективную среду разработки для создания и
использования Web-сервисов.
Никакая часть настоящей статьи не может быть воспроизведена или
передана в какой бы то ни было форме и какими бы то ни было средствами, будь то
электронные или механические, если на то нет письменного разрешения владельцев
авторских прав.
Материал, изложенный в данной статье, многократно
проверен. Но, поскольку вероятность технических ошибок все равно существует,
сообщество не может гарантировать абсолютную точность и правильность приводимых
сведений. В связи с этим сообщество не несет ответственности за возможные
ошибки, связанные с использованием статьи.