Поделиться через


Отправка по элементу "Тело"

В примере AdvancedDispatchByBody показано, как реализовать альтернативный алгоритм назначения входящих сообщений операциям.

По умолчанию диспетчер модели службы выбирает соответствующий метод обработки для входящего сообщения на основе заголовка WS-Addressing "Action" сообщения или эквивалентной информации в HTTP-запросе SOAP.

Некоторые стеки веб-служб SOAP 1.1, которые не следуют рекомендациям по базовым профилям WS-I версии 1.1, отправляют сообщения не на основе URI действия, а на основе XML-имени первого элемента в теле SOAP. Аналогичным образом, клиентская сторона этих стеков может отправлять сообщения с пустым или произвольным заголовком HTTP SoapAction, который был разрешен спецификацией SOAP 1.1.

Чтобы изменить способ отправки сообщений на методы, пример реализует IDispatchOperationSelector интерфейс расширяемости для объекта DispatchByBodyElementOperationSelector. Этот класс выбирает операции на основе первого элемента текста сообщения.

Конструктор класса ожидает словарь, заполненный парами XmlQualifiedName и строками, в которых полные имена указывают на имя первого дочернего элемента тела SOAP, а строки указывают соответствующее имя операции. Название defaultOperationName операции, получающей все сообщения, которые невозможно сопоставить с этим словарем:

class DispatchByBodyElementOperationSelector : IDispatchOperationSelector
{
    Dictionary<XmlQualifiedName, string> dispatchDictionary;
    string defaultOperationName;

    public DispatchByBodyElementOperationSelector(Dictionary<XmlQualifiedName,string> dispatchDictionary, string defaultOperationName)
    {
        this.dispatchDictionary = dispatchDictionary;
        this.defaultOperationName = defaultOperationName;
    }
}

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

В этом примере селектор операций получает XmlDictionaryReader текст входящего сообщения с помощью GetReaderAtBodyContents. Этот метод уже устанавливает позицию парсера на первый дочерний элемент текста сообщения, что позволяет получить имя и URI пространства имен текущего элемента и объединить их в XmlQualifiedName объект, который затем используется для поиска соответствующей операции из словаря, содержащегося селектором операций.

public string SelectOperation(ref System.ServiceModel.Channels.Message message)
{
    XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
    XmlQualifiedName lookupQName = new
       XmlQualifiedName(bodyReader.LocalName, bodyReader.NamespaceURI);
    message = CreateMessageCopy(message,bodyReader);
    if (dispatchDictionary.ContainsKey(lookupQName))
    {
         return dispatchDictionary[lookupQName];
    }
    else
    {
        return defaultOperationName;
    }
}

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

private Message CreateMessageCopy(Message message,
                                     XmlDictionaryReader body)
{
    Message copy = Message.CreateMessage(message.Version,message.Headers.Action,body);
    copy.Headers.CopyHeaderFrom(message,0);
    copy.Properties.CopyProperties(message.Properties);
    return copy;
}

Добавление селектора операций в службу

Селекторы операций диспетчера служб — это расширения диспетчера Windows Communication Foundation (WCF). Для выбора методов в канале обратного вызова дуплексных контрактов существуют также селекторы операций клиента, которые работают точно так же, как селекторы операций диспетчеризации, описанные здесь, но которые не рассматриваются подробно в этом примере.

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

Так как селекторы операций имеют область контракта, соответствующим поведением, которое следует реализовать здесь, является IContractBehavior. Так как интерфейс реализуется в Attribute производном классе, как показано в следующем коде, поведение может быть декларативно добавлено в любой контракт службы. Всякий раз, когда ServiceHost открыт и построена среда выполнения отправки, все действия, обнаруженные как атрибуты для контрактов, операций и реализаций служб или как элементы в конфигурации услуги, автоматически добавляются, после чего их просят внести дополнения или изменить конфигурацию по умолчанию.

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

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)]
class DispatchByBodyElementBehaviorAttribute : Attribute, IContractBehavior
{
    // public void AddBindingParameters(...)
    // public void ApplyClientBehavior(...)
    // public void Validate(...)

Во-первых, реализация настраивает словарь поиска для селектора операций путем итерации элементов в конечной точке службы. Затем каждое описание операции проверяется на наличие поведения DispatchBodyElementAttribute, реализации IOperationBehavior, которая также определена в этом примере. Хотя этот класс также является поведением, оно пассивно и не активно вносит изменения конфигурации во время выполнения диспетчеризации. Все его методы возвращаются вызывающему объекту без каких-либо действий. Поведение операции существует для того, чтобы метаданные, необходимые для нового механизма диспетчеризации, а именно квалифицированное имя элемента body, при наступлении которого выбирается операция, могли быть связаны с соответствующими операциями.

Если такое поведение найдено, в словарь добавляется пара значений, созданная из квалифицированного имени XML (QName свойства) и имени операции (Name свойства).

После заполнения словаря создается новый DispatchByBodyElementOperationSelector с этой информацией и устанавливается в качестве селектора операций среды выполнения диспетчера:

public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
{
    Dictionary<XmlQualifiedName,string> dispatchDictionary =
                     new Dictionary<XmlQualifiedName,string>();
    foreach( OperationDescription operationDescription in
                              contractDescription.Operations )
    {
        DispatchBodyElementAttribute dispatchBodyElement =
   operationDescription.Behaviors.Find<DispatchBodyElementAttribute>();
        if ( dispatchBodyElement != null )
        {
             dispatchDictionary.Add(dispatchBodyElement.QName,
                              operationDescription.Name);
        }
    }
    dispatchRuntime.OperationSelector =
            new DispatchByBodyElementOperationSelector(
               dispatchDictionary,
               dispatchRuntime.UnhandledDispatchOperation.Name);
    }
}

Реализация службы

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

Пример службы проекта применяет DispatchByBodyElementBehaviorAttribute поведение контракта к IDispatchedByBody контракту службы и помечает каждую из двух операций OperationForBodyA() и OperationForBodyB() поведением операции DispatchBodyElementAttribute. При открытии хоста службы, реализующей этот контракт, диспетчер-сборщик собирает эти метаданные, как описано ранее.

Поскольку селектор операций выбирает исключительно на основе элемента тела сообщения и игнорирует "Action", необходимо указать среде выполнения не проверять заголовок "Action" в возвращаемых ответах, назначив подстановочный знак "*" свойству ReplyActionOperationContractAttribute. Кроме того, необходимо иметь операцию по умолчанию, которая имеет свойство Action, заданное подстановочным знаком "*". Операция по умолчанию получает все сообщения, которые не могут быть обработаны и не имеют DispatchBodyElementAttribute.

[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples"),
                            DispatchByBodyElementBehavior]
public interface IDispatchedByBody
{
    [OperationContract(ReplyAction="*"),
     DispatchBodyElement("bodyA","http://tempuri.org")]
    Message OperationForBodyA(Message msg);
    [OperationContract(ReplyAction = "*"),
     DispatchBodyElement("bodyB", "http://tempuri.org")]
    Message OperationForBodyB(Message msg);
    [OperationContract(Action="*", ReplyAction="*")]
    Message DefaultOperation(Message msg);
}

Пример реализации службы прост. Каждый метод упаковывает полученное сообщение в ответное сообщение и повторяет его обратно клиенту.

Запуск и сборка примера

При запуске примера содержимое ответов операции отображается в окне консоли клиента, подобно следующему (отформатированному) выводу.

Клиент отправляет три сообщения в службу, элемент содержимого тела которого называется bodyA, bodyBи bodyXсоответственно. Как можно сделать вывод из предыдущего описания и договора на оказание услуги, входящее сообщение с элементом bodyA отправляется в метод OperationForBodyA(). Так как для сообщения с элементом bodyX body отсутствует явный целевой объект отправки, сообщение отправляется в DefaultOperation(). Каждая из операций службы упаковывает текст полученного сообщения в элемент, характерный для метода, и возвращает его, что позволяет однозначно сопоставить входящие и исходящие сообщения в этом примере.

<?xml version="1.0" encoding="IBM437"?>
<replyBodyA xmlns="http://tempuri.org">
   <q:bodyA xmlns:q="http://tempuri.org">test</q:bodyA>
</replyBodyA>
<?xml version="1.0" encoding="IBM437"?>
<replyBodyB xmlns="http://tempuri.org">
  <q:bodyB xmlns:q="http://tempuri.org">test</q:bodyB>
</replyBodyB>
<?xml version="1.0" encoding="IBM437"?>
<replyDefault xmlns="http://tempuri.org">
   <q:bodyX xmlns:q="http://tempuri.org">test</q:bodyX>
</replyDefault>

Настройка, сборка и запуск примера

  1. Убедитесь, что вы выполнили процедуру настройки One-Time для образцов Windows Communication Foundation.

  2. Чтобы создать решение, следуйте инструкциям по созданию примеров Windows Communication Foundation.

  3. Чтобы запустить пример в конфигурации с одним или несколькими компьютерами, следуйте инструкциям в запуска примеров Windows Communication Foundation.