Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 
Сила параметров XmlElement в Web-методах ASP.NET
Автор: Мэтт Пауэлл (Matt Powell), Корпорация Microsoft
Перевод: Надежда Шатохина(sna@uneta.org), UNETA (http://www.uneta.org/)
18 апреля 2003
Обзор:
Мэтт Пауэлл рассматривает различные способы использования параметров XmlElement с вашим Web-сервисом в целях организации доступа к необработанным XML-данным и получения высокоуровневого контроля.
Содерджание:
  • Введение
  • Сериализация Web-метода
  • Отказ от использования XmlSerializer в Web-методах
  • Сила XmlElement
  • XmlElement и проверка сообщений
  • Атрибут XmlAnyElement
  • Чем больше контроля... тем труднее код
  • Введение

    В статье Тима Эдвардса (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 /c MyNewClass.xsd
    

    Таким образом, я получил 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-сервисов.

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

    Материал, изложенный в данной статье, многократно проверен. Но, поскольку вероятность технических ошибок все равно существует, сообщество не может гарантировать абсолютную точность и правильность приводимых сведений. В связи с этим сообщество не несет ответственности за возможные ошибки, связанные с использованием статьи.
     
         

       
       
         
      VBNet рекомендует