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

Я работаю с WCF для обмена сообщениями с третьей стороной. Сообщения необходимо отправлять и получать в конверте, который соответствует спецификация ebXML. В идеале я хотел бы использовать как можно больше стека WCF и избегать одного метода для обработки всех их, поскольку в этом случае это означало бы написание большей части инфраструктуры 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;
            };
    }
}

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

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

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.

Ссылки справочной документации

Лучшие ссылки, которые я нашел до сих пор, - это статьи журнала MSDN в Сервисной станции (сайт Google: msdn.microsoft.com Service Station WCF).

WCF Bindings in Depth - Очень полезная информация о настройке привязок

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

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

Ответ 3

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

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