Как изменить WCF для обработки сообщений в другом (не SOAP) формате? - PullRequest
11 голосов
/ 14 декабря 2010

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

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

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

Я стремлюсь к чему-то вроде следующего.

Отправляемые и получаемые сообщения ДОЛЖНЫ быть отформатированы, как показано ниже, где имя первого элемента - это имя операции, которая должна быть выполнена, а дочерний элемент - полезная нагрузка сообщения запроса, будет иметь вид:

<?xml version="1.0" encoding="UTF-8"?>
<op:DoSomething xmlns:op="http://my.ebXML.schema.com" xmlns:payload="http://payload.company.com">
    <op:AnObject>
        <payload:ImportantValue>42</payload:ImportantValue>
    </op:AnObject>
</op:DoSomething>

И ответ будет:

<?xml version="1.0" encoding="UTF-8"?>
<op:AcknowledgementResponse xmlns:op="http://my.ebXML.schema.com" xmlns:payload="http://payload.company.com">
    <op:ResponseObject>
        <payload:Ok>True</payload:Ok>
    </op:ResponseObject>
</op:AcknowledgementResponse>

Поскольку все сообщения описываются схемами XML, я использовал XSD.exe для преобразования их в строго типизированные объекты. См. https://gist.github.com/740303 для схем. Обратите внимание, что это примеры схем. Я не могу публиковать настоящие схемы без нарушения клиентских соглашений о конфиденциальности (и вы тоже не захотите меня, потому что они огромные).

Теперь я бы хотел написать реализацию службы следующим образом:

public class MyEndpoint : IMyEndpoint
{
    public AcknowledgementResponse DoSomething(AnObject value)
    {
        return new AcknowledgementResponse
            {
                Ok = True;
            };
    }
}

Любая помощь будет высоко ценится.

Ответы [ 3 ]

12 голосов
/ 16 декабря 2010

Подробности моей реализации ответа Тима

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

Следующие вещи должны были быть реализованы, чтобы WCF мог использоваться так, как мне требовалось:

  1. WCF не ожидает сообщения SOAP
  2. Возможность форматировать сообщения запроса и ответа точно так, как требуется
  3. Все сообщения, подлежащие рассмотрению для обработки
  4. Входящие сообщения будут перенаправлены на правильную операцию для их обработки

1. Настройте WCF так, чтобы он не ожидал сообщения SOAP

Первым шагом было получение входящего сообщения через TextMessageEncoder. Это было достигнуто с помощью пользовательской привязки с параметром MessageVersion.None в элементе textMessageEncoding.

  <customBinding>
    <binding name="poxMessageBinding">
      <textMessageEncoding messageVersion="None" />
      <httpTransport />
    </binding>
  </customBinding>

2. Правильно отформатируйте сообщение

Форматер сообщений требуется, так как входящее сообщение отказалось от десериализации существующим форматером XML без добавления дополнительных атрибутов в контрактах сообщений. Обычно это не было бы проблемой, но при запуске моих клиентских схем ebXML через XSD.exe создается файл cs из 33000 строк, и мне не нужно было никоим образом изменять это. Кроме того, люди забудут повторно добавить атрибуты в будущем, так что, надеюсь, это также облегчит обслуживание.

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

public SimpleXmlFormatter(OperationDescription operationDescription)
{
    // Get the request message type
    var parameters = operationDescription.SyncMethod.GetParameters();
    if (parameters.Length != 1)
        throw new InvalidDataContractException(
"The SimpleXmlFormatter will only work with a single parameter for an operation which is the type of the incoming message contract.");
    _requestMessageType = parameters[0].ParameterType;

    // Get the response message type
    _responseMessageType = operationDescription.SyncMethod.ReturnType;
}

Затем он просто использует XmlSerializer для сериализации и десериализации данных. Интересной частью этого для меня было использование настраиваемого BodyWriter для сериализации объекта в объект Message. Ниже приведена часть реализации сервисного сериализатора. Реализация клиента обратная.

public void DeserializeRequest(Message message, object[] parameters)
{
    var serializer = new XmlSerializer(_requestMessageType);
    parameters[0] = serializer.Deserialize(message.GetReaderAtBodyContents());
}

public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
    return Message.CreateMessage(MessageVersion.None, _responseMessageType.Name,
                                 new SerializingBodyWriter(_responseMessageType, result));
}

private class SerializingBodyWriter : BodyWriter
{
    private readonly Type _typeToSerialize;
    private readonly object _objectToEncode;

    public SerializingBodyWriter(Type typeToSerialize, object objectToEncode) : base(false)
    {
        _typeToSerialize = typeToSerialize;
        _objectToEncode = objectToEncode;
    }

    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
    {
        writer.WriteStartDocument();
        var serializer = new XmlSerializer(_typeToSerialize);
        serializer.Serialize(writer, _objectToEncode);
        writer.WriteEndDocument();
    }
}

3. Обработка всех входящих сообщений

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

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
    endpointDispatcher.ContractFilter = new MatchAllMessageFilter();
    // Do more config ...
}

4. Выбор правильной операции

Это было достигнуто путем создания класса, реализующего IDispatchOperationSelector. В методе SelectOperation я проверяю входящее сообщение. Здесь я ищу две вещи: 1. Проверка работоспособности того, что пространство имен корневого элемента совпадает с пространством имен контракта на обслуживание 2. Имя корневого элемента (обратите внимание на использование LocalName для удаления любого префикса пространства имен)

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

public string SelectOperation(ref Message message)
{
    var messageBuffer = message.CreateBufferedCopy(16384);

    // Determine the name of the root node of the message
    using (var copyMessage = messageBuffer.CreateMessage())
    using (var reader = copyMessage.GetReaderAtBodyContents())
    {
        // Move to the first element
        reader.MoveToContent();

        if (reader.NamespaceURI != _namespace)
            throw new InvalidOperationException(
"The namespace of the incoming message does not match the namespace of the endpoint contract.");

        // The root element name is the operation name
        var action = reader.LocalName;

        // Reset the message for subsequent processing
        message = messageBuffer.CreateMessage();

        // Return the name of the action to execute
        return action;
    }
}

Завершение всего этого

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

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

Полезные ссылки на документацию

Лучшие ссылки, которые я нашел, - это статьи в журнале Service Station MSDN (Google "site: msdn.microsoft.com service station WCF").

Привязки WCF в глубине - Очень полезная информация о настройке привязок

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

Примеры Microsoft WCF - Здесь есть много того, что не очень хорошо задокументировано в других местах.Я нашел чтение исходного кода для некоторых из них очень поучительным.

4 голосов
/ 15 декабря 2010

Не думаю, что вам нужно что-то делать с привязками.Я предполагаю, что вам все равно нужно отправлять сообщение в формате ebXML через HTTP?

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

Я думаю, что вам нужно сделать, это реализовать Пользовательский форматировщик сообщений .В частности, поскольку вы говорите, что хотите отправлять сообщения сторонним разработчикам, я думаю, что вам нужно реализовать только интерфейс IClientMessageFormatter.Другой интерфейс (IDispatchMessageFormatter) используется на стороне сервера.

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

После реализации вы можете использовать подход «один метод для обработки их всех» для тестирования и отладки вашего средства форматирования.Просто возьмите полученное сообщение и выведите его на консоль, чтобы вы могли просмотреть его, а затем отправьте ответ ebXML обратно.Вы также можете использовать тот же подход для создания модульного тестирования.

1 голос
/ 14 декабря 2010

Для пользовательского формата сообщения вам нужно Custom MessageEncoder . MSDN содержит пример создания пользовательского кодировщика.Если вы используете Reflector , вы найдете несколько реализаций кодировщика, чтобы вы могли научиться писать.

Вы также можете проверить, что произойдет, если вы попытаетесь использовать TextMessageEncoder с MessageVersion.Noneникогда не пробовал).

...