Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 
Проверка утверждений с помощью WS-Policy и WSE 2.0
Автор: Aaron Skonnard, Northface University (http://www.skonnard.com)
Перевод: Шатохина Надежда(sna@uneta.org), Ukraine .Net Alliance (http://www.uneta.org/)
Март 2004
Применяется к:
  • Web Services
  • WSE 2.0
  • Обзор:
    В данной статье рассмотрено решение проверки утверждений с помощью WS-Policy и WSE 2.0.
    Содерджание:
  • Введение
  • Введение в WS-Policy
  • Утверждение MessagePredicate
  • Политики в WSE 2.0
  • Специальные обработчики утверждений политики
  • Специальный MessagePredicateHandler
  • Функции расширения политики
  • На чем мы остановились?
  • Введение

    В выпусках журнала MSDN® Magazine за июль и август Дэном Салливаном и мною были рассмотрены различные техники Web-сервисов проверки достоверности. Мы обратили внимание на тот факт, что инфраструктура ASP.NET Web-сервисов (ASMX) не проводит проверки достоверности XML Schema поступающих сообщений и привели возможные типы проблем, которые могут возникнуть в результате этого. (В конце этой статьи приведены URL этих статей.)

    Однако даже с проверкой достоверности XML Schema существуют различные бизнес-правила, которые не могут быть выражены в описаниях составных типов. В результате большинство разработчиков перестали встраивать код проверки достоверности в реализации своих WebMethod. Например, следующий WebMethod перед выполнением непосредственного задания проверяет достоверность того, что длина больше, чем ширина:

    WebService(Namespace="http://example.org/geometry/")]
    public class Geometry
    {
      [WebMethod]
      public double CalcArea(double length, double width)
      {
        if (length <= width)
            throw new 
          Exception("length must be greater than width");
        return length * width;
      }
    }
    

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

    Чтобы исправить эту ситуацию, Дэн и я показали вам, как реализовать класс SoapExtension (называемый ValidationExtension), который предоставляет стандартную проверку достоверности XML Schema, а также более сложные схемы проверки достоверности, основанные на утверждениях XPath. Возможность описания ограничений с помощью утверждений XPath позволяет реализовывать различные бизнес-правила, которые не могли быть выражены с помощью только XML Schema. В нашем примере можно «включить» проверку достоверности, добавляя некоторые атрибуты в ваши WebMethod:

    [WebService(Namespace="http://example.org/geometry/")]
    [AssertNamespaceBinding("m","http://example.org/math")]
    public class Geometry
    {
      [WebMethod]
      [Validation] // включаем проверку достоверности XML Schema
      [Assert("//m:length > //m:width", "length must be greater than width")]
      public double CalcArea(double length, double width)
      {
        return length * width;
      }
    }
    

    Таким образом, мы перемещаем логику проверки достоверности из реализации WebMethod в декларативное выражение (//t:length > //t:width). Теперь, когда бы ни поступило SOAP-сообщение, нацеленное на эту операцию, инфраструктура ASMX вызывает ваш ValidationExtension и предоставляет ему возможность проверить достоверность сообщения в соответствии с объявленными утверждениями. В данном случае, если длина не больше ширины, тогда ValidationExtension перед вызовом WebMethod возвращает SOAP-ошибку. Такой подход делает логику проверки достоверности более простой для выражения, выявления и обслуживания по сравнению с код-центричным подходом.

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

    [WebService(Namespace="http://example.org/geometry/")]
    public class Geometry
    {
      [WebMethod]
      public double CalcArea(double length, double width)
      {
        return length * width;
      }
    }
    

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

    Введение в WS-Policy

    Спецификация WS-Policy появилась как общая инфраструктура для выражения утверждений Web-сервиса (более подробно смотрите в статье «Понимание WS-Policy (http://www.uneta.org/Default.aspx?mnuid=E5ED8936-5C6D-4DFD-8A3A-7CDAD3923183&artID=21665A78-D204-4593-92AA-E7607B31607B)»). Выражение политики — это просто XML-файл, описывающий требования, возможности или предпочтения операций вашего Web-сервиса. Следующий пример показывает базовую структуру выражения политики:

    <wsp:Policy 
        xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy" 
        xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
        wsu:Id="..." Name="..." TargetNamespace="...">
    
        <!-- здесь располагаются утверждения политики --> 
    
    </wsp:Policy>
    

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

    Сама WS-Policy не описывает никаких утверждений политики; он просто определяет инфраструктуру для их стандартной упаковки и обработки. Спецификации WS-PolicyAssertions и WS-SecurityPolicy определяют некоторые стандартные утверждения, касающиеся общих требований приложений Web-сервисов. Также WS-PolicyAssertions определяет полезное утверждение MessagePredicate, которое обобщенно проверяет достоверность определенных пользователем выражений. MessagePredicate может использоваться для проверки разнообразных требований сообщений (таких как бизнес-правила), поэтому он хорошо подходит для улучшения описанного здесь решения проверки достоверности ASMX.

    Утверждение MessagePredicate

    Утверждение MessagePredicate делает возможным гарантировать то, что сообщение соответствует заданному «предикату» (утверждению). MessagePredicate имеет следующую XML-структуру:

    <wsp:MessagePredicate wsp:Usage="..."? Dialect="..."? >
      <!-- здесь располагается утверждение -->
    </wsp:MessagePredicate>
    

    MessagePredicate допускает поддержку нескольких диалектов утверждений. Используемый вами диалект вы задаете с помощью атрибута Dialect. В настоящее время спецификация определяет только два диалекта: XPath 1.0 и диалект специальной части сообщения (смотрите таблицу1). Поскольку в предыдущем решении я работал с XPath 1.0, я буду продолжать использовать его с MessagePredicate:

    <wsp:Policy 
        xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy" 
        xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
        xmlns:m=" http://example.org/math"
        wsu:Id="urn:CalcDistance"
    >
        <!-- здесь располагаются утверждения политики --> 
        <wsp:MessagePredicate wsp:Usage="wsp:Required">
           //m:length > //m:width
        </wsp:MessagePredicate>
    </wsp:Policy>
    

    Используя этот файл политики вместе с WebMethod-реализацией CalcDistance, вы можете получить аналогичную логику проверки достоверности без необходимости ее введения в код. Теперь вам всего лишь надо решить, как реализовать поддержку утверждений MessagePredicate, используя Расширения Web-сервисов (Web Services Enhancements -WSE) 2.0.

    URI Описание
    http://www.w3.org/TR/1999/REC-xpath-19991116 Используется по умолчанию. Содержимое элемента <MessagePredicate> — выражение XPath 1.0. Выражение XPath сравнивается с узлом элемента soap:Envelope, в результате получаем значение true или false.
    http://schemas.xmlsoap.org/2002/12/wsse#part Содержимое элемента <wsp:MessagePredicate> — список необходимых частей сообщения. Список частей сообщения сравнивается с узлом элемента soap:Envelope, в результате получаем значение true или false; значение true возвращается, если все указанные части не пустые.

    Таблица 1. Диалекты MessagePredicate

    Политики в WSE 2.0

    Инфраструктура ASP.NET Web-сервисов поставляется без какой бы то ни было встроенной поддержки WS-Policy, WS-PolicyAssertions или любых других WS-спецификаций, касающихся этого вопроса. В настоящее время Microsoft предоставляет поддержку для этих спецификаций через инструментальные средства WSE 2.0, которые вводятся в инфраструктуру ASMX как SoapExtension (Soap-расширение). Кроме интеграции с ASP.NET, WSE 2.0 обеспечивает независимый от средств транспортировки API обмена сообщениями, который поддерживает эти спецификации. Это означает, что вы можете пользоваться преимуществом WS-Policy при использовании других транспортных протоколов, например, TCP.

    После того, как вы установили WSE 2.0 (выбирая настройки Visual Studio® .NET Developer), вы можете добавить поддержку WSE в свой проект с помощью инструмента WSE Settings. Вы получаете доступ к нему, щелкнув правой кнопкой свой проект в Solution Explorer и выбрав WSE Settings. Чтобы включить поддержку WS-Policy, отметьте обе метки. После этого ваш проект будет содержать ссылку на сборку Microsoft.Web.Services, в вашем файле web.config появится несколько новых строк, и он будет выглядеть примерно, как показано в листинге 2.

    Листинг 2: Измененный Web.config

    <configuration>
      <configSections>
        <section name="microsoft.web.services" 
         type="Microsoft.Web.Services.Configuration.WebServicesConfiguration, 
         Microsoft.Web.Services, Version=2.0.0.0, Culture=neutral, 
         PublicKeyToken=31bf3856ad364e35" />
      </configSections>
      <system.web>
        <webServices>    
          <soapExtensionTypes>
            <add type="Microsoft.Web.Services.WebServicesExtension, 
             Microsoft.Web.Services, Version=2.0.0.0, Culture=neutral, 
             PublicKeyToken=31bf3856ad364e35" priority="1" group="0" />
          </soapExtensionTypes>
        </webServices>
      </system.web>
    </configuration>
    

    Теперь вы можете конфигурировать поддержку WS-Policy с помощью тэга Policy. WSE поддерживает представление политик как «получающей стороны», так и «отправляющей стороны». Политика «получающей стороны» реализовывает утверждения политики при получении сообщений, в то время как политика «отправляющей стороны» реализовывает утверждения политики при отправке сообщений.

    Вы включаете WSE 2.0-поддержку политики, определяя кэш-файл политики для необходимых вам стороны (сторон). Добавьте элемент policy в файл web.config своего проекта, определяя кэш политики для каждой стороны:

    <configuration>
      <microsoft.web.services>
        <policy>
          <receive>
            <cache name="policyCache.xml" />
          </receive>
          <send>
            <cache name="policyCache.xml" />
          </send>
        </policy>
      </microsoft.web.services>
      •••
    </configuration>
    

    Формат кэш-файла политики делает возможным связать утверждения политики с конкретными конечными точками Web-сервиса в рамках вашего проекта. Инструмент WSE Settings также предоставляет возможность создания или редактирования файлов политики и определяет связывание конечных точек через встроенный редактор политики. Редактор политики может помочь вам сформировать базовую структуру кэш-файла политики. Он сгенерирует ID для ваших утверждений политики и автоматически свяжет их с заданной конечной точкой, как показано в листинге 3. Затем вы можете вручную вводить утверждения MessagePredicate в предоставленный элемент политики.

    Листинг 3: Кэш-файл политики

    <policyDocument
      xmlns="http://schemas.microsoft.com/wse/2003/06/Policy"
      xmlns:wse="http://schemas.microsoft.com/wse/2003/06/Policy"
      xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
      xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"
      xmlns:m="http://example.org/math
    >
    
      <!-- в этой секции конечные точки связываются
           с выражениями политик -->
      <mappings>
        <map to="http://localhost/mathservice/math.asmx">
          <default policy="#policy-600686d0-b855-4f6b-9dac-9218547d462c"/>
        </map>
      </mappings>
    
      <!-- в этой секции находятся выражения политики -->
      <policies >
        <wsp:Policy wsu:Id="policy-600686d0-b855-4f6b-9dac-9218547d462c">
    
          <!-- утверждения MessagePredicate располпгаются здесь, мне пришлось
               добавлять эти элементы вручную -->
          <wsp:MessagePredicate wsp:Usage="wsp:Required">
            //m:length > //m:width
          </wsp:MessagePredicate>
          •••
        </wsp:Policy>
      </policies>
    </policyDocument>
    

    При условии, что ваше приложение сконфигурировано правильно, WSE 2.0 автоматически обработает утверждения MessagePredicate, но только те из них, которые используют диалект частей SOAP-сообщения (Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part"). Если это единственный необходимый вам диалект, просто добавьте элементы MessagePredicate в свой кэш-файл политики, и на этом все. Встроенная поддержка политики будет проводить проверку на соответствие вашим утверждениям MessagePredicate и возвращать SOAP-ошибки в случае невыполнения условий.

    WSE 2.0 не предоставляет поддержки для всего диалекта XPath (Dialect="http://www.w3.org/TR/1999/REC-xpath-19991116"). Это плохо, потому что XPath — очень мощный язык для описания ограничений сообщения. И вам на самом деле нужен полная версия диалекта XPath, чтобы выражать традиционные бизнес-правила в XML-мире. К счастью WSE 2.0 обеспечивает расширяемую модель для написания специальных обработчиков утверждений. Вы можете использовать преимущество возможности расширения для переопределения встроенной функциональности MessagePredicate и внедрения поддержки полной версии диалекта XPath.

    Специальные обработчики утверждений политики

    Процесс написания специальных обработчиков утверждений политики состоит из получения нового класса из PolicyAssertion (в пространстве имен Microsoft.Web.Services.Policy) и регистрации его в файле web.config. следующий код иллюстрирует, как получить новый класс MessagePredicateHandler из PolicyAssertion:

    using Microsoft.Web.Services.Policy;
    
    public class MessagePredicateHandler : PolicyAssertion
    {
       // здесь переопределите члены...
    }
    

    Затем вы должны добавить элемент <assertion> в секцию <policy> файла web.config. Элемент <assertion> связывает имя элемента утверждения политики (в данном случае, wsp:MessagePredicate) в класс обработчика, как показано в следующем фрагменте кода:

    <configuration>
      <microsoft.web.services>
        <policy>
          <receive>
            <cache name="policyCache.xml" />
          </receive>
          <assertion name="wsp:MessagePredicate"  
           type="MessagePredicateHandler,MessagePredicateHandler" 
           xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy" />
        </policy>
      </microsoft.web.services>
      •••
    </configuration>
    

    После регистрации класса инфраструктура WSE будет использовать его для обработки элемента <policy>, определенного в файле конфигурации.

    В PolicyAssertion всего лишь два метода, которые в производном классе вы должны переопределить: IsSatisfiedBy и CanEnforce, как показано здесь:

    public abstract class PolicyAssertion : PolicyElement
    {
       protected PolicyAssertion(XmlQualifiedName qName,
          XmlElement element);
       protected PolicyAssertion(XmlQualifiedName qName,
          XmlElement element, bool usageRequired);
    
       protected override void LoadXml(XmlElement element);
       public abstract bool IsSatisfiedBy(SoapEnvelope message);
       public abstract bool CanEnforce(ref IPolicyEnforcer enf);
    }
    

    Полученный вами класс также должен содержать конструктор, принимающий единственный аргумент типа XmlElement. Для каждого выражения политики зарегистрированного кэш-файла политики создается новый экземпляр вашего производного класса. Это происходит при первом поступлении сообщения в конечную точку. Такой же объект затем используется для обработки выражений политики при вызове методов.

    Конструктор — это ваш шанс сохранить информацию, находящуюся в элементе утверждения, для дальнейшего использования. Поставляемый объект XmlElement содержит синтаксически разобранный XML, используемый в выражении политики. В случае MessagePredicate вам надо сохранять выражение MessagePredicate в локальном поле, чтобы вы могли использовать его позже для проверки достоверности поступающих сообщений.

    Метод IsSatsifiedBy вызывается для каждого сообщения, проходящего по конвейеру, для проверки того, что поступающие сообщения удовлетворяют утверждению. Если сообщение удовлетворяет утверждению, оно продолжает перемещение по конвейеру. Если сообщение не удовлетворяет утверждению при получении, инфраструктура политики возвращает SOAP-ошибку. Если сообщение не удовлетворяет утверждению при отправке, инфраструктура политики вызывает CanEnforce, чтобы определить, поддерживает ли обработчик автоматическую реализацию (например, некоторые утверждения WS-SecurityPolicy поддерживают эту возможность, автоматически подписывая/кодируя сообщение перед отправкой). Если вы хотите поддерживать эту возможность в специальном обработчике, вы должны написать еще один класс, реализовывающий интерфейс IPolicyEnforcer, как показано здесь:

    public interface IPolicyEnforcer
    {
       void Enforce(SoapEnvelope message);
    }
    

    Просто верните экземпляр вашего специального класса из метода CanEnforce, и инфраструктура политики будет использовать его для вызова метода Enforce в случае, когда сообщение не будет удовлетворять утверждению при отправке. Метод Enforce дает вам возможность изменить сообщение так, чтобы оно удовлетворяло утверждению.

    Теперь давайте рассмотрим полную реализацию MessagePredicateHandler.

    Специальный MessagePredicateHandler

    Реализовать обработчик для утверждения MessagePredicate просто, если вы хорошо знакомы с API System.Xml. Основной объем работы выполняется в конструкторе (смотрите листинг 4). В моей реализации я вызываю базовый конструктор, т.е. он может обрабатывать стандартные атрибуты WS-Policy, например, wsp:Usage. Затем я проверяю наличие атрибута Dialect, чтобы определить, какой язык выражений использовался. Далее я создаю XPathNavigator, который могу использовать для компиляции выражения, содержащегося в элементе MessagePredicate. Обратите внимание, что когда используются части диалекта, я преобразовываю выражение частей в эквивалентное XPath-выражение, таким образом, и с этого момента я могу использовать такую же логику проверки достоверности.

    Листинг 4: Описание MessagePredicateHandler

    public class MessagePredicateHandler : PolicyAssertion
    {
        private const string WP_NS = 
           "http://schemas.xmlsoap.org/ws/2002/12/policy";
        private const string DIALECT_XPATH = 
           "http://www.w3.org/TR/1999/REC-xpath-19991116";
        private const string DIALECT_PARTS = 
           "http://schemas.xmlsoap.org/2002/12/wsse#part";
    
        private static XmlQualifiedName qname =
            new XmlQualifiedName("MessagePredicate", WP_NS);
        private XPathExpression xe = null; 
        private Dialect dialect = Dialect.XPath; 
    
        private enum Dialect
        {
           XPath, 
           Parts
        }
    
        public MessagePredicateHandler(XmlElement element) : 
            base(qname, element) 
        {
           int id = this.GetHashCode();
           base.LoadXml(element); 
           if (element.HasAttribute("Dialect"))
           {
              switch(element.GetAttribute("Dialect"))
              {
                 case DIALECT_XPATH: 
                    dialect = Dialect.XPath; 
                    break; 
                    case DIALECT_PARTS:
                    dialect = Dialect.Parts; 
                    break; 
              }
           }
           XPathNavigator nav = element.CreateNavigator();
    
           if (dialect == Dialect.Parts) 
           {
              string[] parts = element.InnerText.Split(' '); 
              StringBuilder partsExp = new StringBuilder();
              for (int i=0; i<parts.Length; i++)
              {
                 if (i!=0) 
                    partsExp.Append(" and ");
                 partsExp.Append(String.Format("boolean({0})", parts[i])); 
              }
              xe = nav.Compile(partsExp.ToString());
           }
           else
              xe = nav.Compile(String.Format("boolean({0})", 
                   element.InnerText)); 
    
           XmlNamespaceManager ctx = new XmlNamespaceManager(new NameTable());
           XPathNodeIterator it = nav.Select("namespace::*"); 
           while (it.MoveNext())
              if (!it.Current.LocalName.StartsWith("xml"))
                 ctx.AddNamespace(it.Current.LocalName, it.Current.Value); 
           xe.SetContext(ctx); 
        }
    
        public override bool CanEnforce(ref IPolicyEnforcer enforcer) 
        {
           // мы не поддерживаем автоматического применения 
           return false; 
        }
    
        public override bool IsSatisfiedBy(SoapEnvelope message) 
        {
           XPathNavigator nav = message.CreateNavigator();
           return (bool)nav.Evaluate(xe); 
        }
    }
    

    Сгенерировав окончательное XPath-выражение, я добавляю его в объект XPathExpression, который сохраняется в частном поле экземпляра xe. Хитрость реализации в том, что вам надо загрузить связи пространства имен из документа политики в объект XmlNamespaceManager. Это позволяет объекту XPathExpression определить префиксы пространства имен в выражениях MessagePredicate. Вместе с этим объект XPathExpression готов к использованию в IsSatisfiedBy.

    Реализация IsSatisfiedBy просто создает новый XPathNavigator для доставляемого сообщения и вызывает Evaluate для обработки кэшированного объекта XPathExpression. Если выражение возвращает значение true, сообщение удовлетворяет утверждению. Автоматическое применение не поддается обработке произвольными XPath-выражениями, поэтому CanEnforce просто возвращает значение false.

    Функции расширения политики

    Начав написание выражений MessagePredicate, вы заметите, что существуют несколько узлов, которые вам надо определять снова и снова, такие как элементы soap:Body и soap:Header, как показано здесь:

    <wsp:MessagePredicate wsp:Usage="wsp:Required"
      xmlns:wsp='://schemas.xmlsoap.org/ws/2002/12/policy '
      xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'
      xmlns:m='http://example.org/math'
    >
    
      /s:Envelope/s:Body/m:CalcDistance/m:length >
      /s:Envelope/s:Body/m:CalcDistance/m:width
    
    </wsp:MessagePredicate>
    

    Это не только делает трудно разбираемым громоздкое «/s:Envelope/s:Body», а также связывает выражение с определенной версией SOAP (в данном случае SOAP 1.1). когда инфраструктура начинает поддерживать SOAP 1.2, это выражение всегда будет приводить к сбою. Чтобы упростить задачу по написанию этих выражений, авторы WS-PolicyAssertion определили набор функций расширения, который предусматривает наиболее распространенные выражения SOAP.

    Существует два набора функций расширения: один — для использования в выражениях XPath 1.0, а другой — для использования в выражениях частей сообщения (смотрите Таблицу 2). Чтобы полностью поддерживать утверждение MessagePredicate, вы должны расширить реализацию MessagePredicateHandler для поддержки этих дополнительных функций.

    Функция политики Описание
    wsp:GetBody (node) Возвращает элемент <S:Body> SOAP-конверта из указанного элемента Envelope
    wsp:IsInBody (node) Возвращает значение true, если заданный узел находится в элементе <S:Body> SOAP-конверта из указанного элемента Envelope
    wsp:GetHeader (node) Возвращает элемент <S:Header> SOAP-конверта из указанного элемента Envelope
    wsp:IsInHeader (node) Возвращает значение true, если заданный узел находится в элементе <S:Header> SOAP-конверта из указанного элемента Envelope
    wsp:RoleURIForHeaderBlock (node) Возвращает SOAP-роль заданного блока заголовка
    wsp:IsMandatoryHeaderBlock (node) Возвращает значения true/false в зависимости от того, является ли указанный блок заголовка обязательным или нет (mustUnderstand = true)
    wsp:IsRoleURIForNext (node, string) Возвращает значения true/false в зависимости от того, соответствует ли указанная роль предопределенной «следующей» роли для версии SOAP, используемой поставляемым сообщением
    wsp:IsRoleURIForUltimateReceiver (node, string) Возвращает значения true/false в зависимости от того, соответствует указанная роль предопределенному «конечному получателю» для версии SOAP, используемой поставляемым сообщением
    wsp:GetNodesetForNode (node) Возвращает набор XPath Node для узла (включая узел, его атрибуты, всех его потомков и их атрибуты)
    Функция выбора части сообщения Описание
    wsp:Body() Определяет «тело» сообщения
    wsp:Header(x) Определяет «заголовок», имя которого указано в QName

    Таблица 2. Функции расширения

    К счастью, System.Xml делает возможным написание специальных функций, которые могут быть введены в механизм обработки XPath. Для этого вы должны сначала написать специальный класс XsltContext, который вы поставляете в ваш откомпилированный объект XPathExpression (смотрите листинг 4). В вашем производном классе переопределите ResolveFunction, чтобы задать класс, который механизм XPath должен использовать для обработки каждой функции расширения.

    Листинг 4: Описание MessagePredicateContext

    public class MessagePredicateContext : XsltContext
    {
        private const string WP_NS = 
       "http://schemas.xmlsoap.org/ws/2002/12/policy";
        public XmlNamespaceManager NamespaceManager; 
    
        public MessagePredicateContext() : base() {}
        public MessagePredicateContext(NameTable nt):base(nt) {}
        public MessagePredicateContext(XmlNamespaceManager ns) 
    { 
       this.NamespaceManager = ns; 
    }
    
        public override int CompareDocument(string baseUri, 
           string nextbaseUri) 
        {
            return 0; 
        }
    
        public override bool PreserveWhitespace(
            XPathNavigator node) 
        {
            return false; 
        }
    
        public override IXsltContextFunction ResolveFunction(
           string prefix, string name, XPathResultType[] ArgTypes) 
        {
           if (this.LookupNamespace(prefix).Equals(WP_NS)) 
           {
              switch(name) 
              {
                 case "Body":
                    return new BodyFunction();
                 case "Header":
                    return new HeaderFunction();
                 case "GetBody":
                    return new GetNodesetFunction("GetBody");
                 case "GetHeader":
                    return new GetNodesetFunction("GetHeader");
                 case "GetNodesetForNode":
                    return new GetNodesetFunction(
                       "GetNodesetForNode");
                 case "IsInBody":
                    return new IsFunction("IsInBody");
                 case "IsInHeader":
                    return new IsFunction("IsInHeader");
                 case "IsMandatoryHeaderBlock":
                    return new IsFunction(
                       "IsMandatoryHeaderBlock");
                 case "RoleURIForHeaderBlock":
                    return new RoleURIForHeaderBlockFunction();
                 case "IsRoleURIForNext":
                    return new IsRoleURIFunction(
                       "IsRoleURIForNext");
                 case "IsRoleURIForUltimateReceiver":
                    return new IsRoleURIFunction(
                       "IsRoleURIForUltimateReceiver");
                 default: 
                    return null; 
              }
           }
           return null; 
        }
    
        public override IXsltContextVariable ResolveVariable(
          string prefix, string name) 
        {
            return null; 
        }
    
        public override bool Whitespace
        {
            get { return false; }
        }
    
        public override string LookupNamespace(string prefix) 
        {
           if (prefix == null || prefix == String.Empty) 
              return String.Empty; 
       else
          return NamespaceManager.LookupNamespace(
             this.NamespaceManager.NameTable.Get(prefix)); 
        }
        
        public override string DefaultNamespace
        {
            get { return String.Empty; }
        }
    }
    

    Затем вы пишите отдельный класс для обработки каждой функции (или, по крайней мере, функцию с некоторыми характеристиками). Этот класс должен реализовывать IXsltContextFunction. Ваша реализация определяет информацию об аргументах и возвращаемом значении и предоставляет реализацию метода Invoke. В листинге 5 приведен полный пример реализации функции wsp:Body.

    Листинг 5: Описание BodyFunction

    public class BodyFunction : IXsltContextFunction
    {    
        public XPathResultType[] ArgTypes
        {
            get { return null; }
        }
        public XPathResultType ReturnType
        {
            get { return XPathResultType.NodeSet; }
        }
        public int Minargs
        {
            get { return 0; }
        }
        public int Maxargs
        {
            get { return 0; }
        }
        public object Invoke(XsltContext xsltContext, 
            object[] args, XPathNavigator docContext) 
        {
           if (args.Length != 0) 
              throw new ArgumentException("expected 0 args");
            
           MessagePredicateContext mctx = 
               xsltContext as MessagePredicateContext; 
           if (mctx == null) 
              throw new InvalidCastException(
             "MessagePredicateContext");
            
           XPathExpression xe = docContext.Compile(
               SoapVersionManager.GetBodyExpression(docContext, 
               mctx.NamespaceManager)); 
           xe.SetContext(mctx.NamespaceManager); 
           return docContext.Select(xe); 
        }
    }
    

    Чтобы применять эти функции в MessagePredicateHandler, вам надо использовать MessagePredicateContext с откомпилированным объектом XPath-выражения. Вы можете сделать это, вводя новую строку кода в конце конструктора в листинге 4, как показано здесь:

     
       XmlNamespaceManager ctx = 
          new XmlNamespaceManager(new NameTable());
       XPathNodeIterator it = nav.Select("namespace::*");
       while (it.MoveNext())
          if (!it.Current.LocalName.StartsWith("xml"))
             ctx.AddNamespace(it.Current.LocalName, 
                it.Current.Value);
    
       // эта строка добавлена для поддержки функций расширения
       MessagePredicateContext mctx = 
          new MessagePredicateContext(ctx);
       xe.SetContext(mctx);
    }
    
    На чем мы остановились?

    С этой завершенной реализацией и необходимыми настройками конфигурации WSE вы готовы использовать утверждение MessagePredicate в своих выражениях политики. В листинге 6 показан пример выражения политики, которое я привязал к конечной точке math.asmx, показанной ранее.

    Листинг 6: Политика для math.asmx

    <wsp:Policy 
       wsu:Id="policy-600686d0-b855-4f6b-9dac-9218547d462c" 
       xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy" 
       xmlns:m="http://example.org/math"
    >
        <wsp:MessagePredicate wsp:Usage="wsp:Required">
         GetBody(.)/m:CalcDistance/m:length div 
         GetBody(.)/m:CalcDistance/m:width = 2
        </wsp:MessagePredicate>
    
        <wsp:MessagePredicate wsp:Usage="wsp:Required" 
         Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">
         wsp:Body()/m:CalcDistance
         wsp:Body()/m:CalcDistance/m:length 
         wsp:Body()/m:CalcDistance/m:width 
         wsp:Header("m:MyCustomHeader")
        </wsp:MessagePredicate>
        
    </wsp:Policy>
    

    Это выражение политики требует, чтобы сообщение содержало элементы m:CalcDistance, m:length и m:width и заголовок :MyCustomHeader. Также требуется, чтобы значение m:length было в два раза больше значения m:width. Если вы представляете следующее сообщение, которое удовлетворяет выражению политики, WebMethod успешно возвращается:

    <soap:Envelope 
       xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Header>
        <foo xmlns="http://example.org/math"/>
      </soap:Header>
      <soap:Body>
        <CalcDistance xmlns="http://example.org/math">
          <length>5</length>
          <width>2.5</width>
        </CalcDistance>
      </soap:Body>
    </soap:Envelope>
    

    Если вы измените значение длины, и оно не будет в два раза превышать значения ширины, вы получите следующую SOAP-ошибку:

    <soap:Fault>
       <faultcode>soap:Client</faultcode>
       <faultstring>The message does not conform to the policy it was mapped
           to.</faultstring>
       <faultactor>http://localhost/mathservice/math.asmx </faultactor>
    </soap:Fault>
    

    Полная реализация MessagePredicateHandler доступна для скачивания по данной ссылке (http://msdn.microsoft.com/msdnmag/issues/04/03/XMLFiles/default.aspx). (Примечание: пример откомпилирован в предварительной версии WSE 2.0 и, возможно, не будет компилироваться в WSE 2.0, когда она выйдет.) Реализация включает все функции расширения, перечисленные в Таблице 2, и пример приложения, которое показывает их в действии.

    Использование выражений политики для выражения требований Web-сервисов делает возможным полностью разделить логику проверки достоверности и другую полезную функциональность от вашего кода. Спецификации WS-Policy, WS-PolicyAssertions и WS-SecurityPolicy заложили основу для этой модели, а WSE 2.0 предоставляет базовую реализацию основных компонентов. WSE 2.0 также предлагает мощную модель расширяемости, которая позволяет вам самостоятельно создавать специальные обработчики утверждений политики.

    Более подробная информация представлена в следующих статьях:

    • «Web Services Policy Framework (WS-Policy) (http://msdn.microsoft.com/library/en-us/dnglobspec/html/ws-policy.asp)»
    • «Web Services Policy Assertions Language (WS-PolicyAssertions) (http://msdn.microsoft.com/library/en-us/dnglobspec/html/ws-policyassertions.asp) »
    • «Web Services Enhancements (WSE) 2.0 Technology Preview (http://www.microsoft.com/downloads/details.aspx?FamilyId=21FB9B9A-C5F6-4C95-87B7-FC7AB49B3EDD)»
    • Также смотрите «Extend the ASP.NET WebMethod Framework with Business Rules Validation (http://msdn.microsoft.com/msdnmag/issues/03/08/BusinessRules/)»
    • «Extend the ASP.NET WebMethod Framework by Adding XML Schema Validation (http://msdn.microsoft.com/msdnmag/issues/03/07/XMLSchemaValidation/)»

    Ваши вопросы и комментарии присылайте по адресу xmlfiles@microsoft.com (mailto:xmlfiles@microsoft.com) для Аарона.

    Аарон Сконнард (Aaron Skonnard) преподает в Northface университете в г. Солт-Лейк-Сити. Аарон является соавтором книг Essential XML Quick Reference (Addison-Wesley, 2001) и Essential XML (Addison-Wesley, 2000) и часто выступает на конференциях. Обращайтесь к нему по адресу http://www.skonnard.com (http://www.skonnard.com/).

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

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

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