Как изменить WCF для обработки сообщений в другом (не SOAP) формате?
Я работаю с WCF для обмена сообщениями со сторонней компанией. Сообщения должны быть отправлены и получены в конверте, который соответствует спецификации ebXML. В идеале я хотел бы использовать как можно большую часть стека WCF и избегать использования одного метода для их обработки, так как в этом случае это будет означать повторное написание большей части инфраструктуры WCF.
Насколько я вижу из моего первоначального исследования, это потребовало бы от меня написания собственного пользовательского связывания, но я изо всех сил пытаюсь найти ясность в документации в MSDN.
Мне удалось найти много подробных документов в отдельных реализациях каждого из них, но очень мало о том, как соединить все это до конца. Похоже, что мои книги также освещают эти темы, не упоминая об этом в "Про 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 ответа
Я не думаю, что вам нужно что-то делать с привязками. Я предполагаю, что вам все равно нужно отправлять сообщение в формате ebXML через HTTP?
Ответ @ladislav - один из подходов, но я думаю, что кодировщики сообщений предназначены для работы на гораздо более низком уровне, чем вы пытаетесь достичь. По сути, они представляют собой фрагменты, которые кодируют сообщения в основной поток и из него (т. Е. Как сообщение представляется в виде байтов в потоке).
Я думаю, что вам нужно сделать, это реализовать собственный форматировщик сообщений. В частности, поскольку вы говорите, что хотите отправлять сообщения сторонним лицам, я думаю, что это только IClientMessageFormatter
интерфейс, который вам нужно реализовать. Другой интерфейс (IDispatchMessageFormatter
) используется на стороне сервера.
Вам также потребуется реализовать соответствующий ServiceBehavior и OperationBehavior для установки средства форматирования в стек, но код для этого будет минимальным (основная часть кода будет реализована в вышеупомянутом интерфейсе).
После реализации вы можете использовать подход "один метод для обработки всех", чтобы протестировать и отладить ваш форматтер. Просто возьмите полученное сообщение и выведите его на консоль, чтобы вы могли просмотреть его, а затем отправьте ответ ebXML обратно. Вы также можете использовать тот же подход для создания вашего юнит-тестирования.
Подробности моей реализации ответа Тима
Мне нужно было написать это для клиента, на которого я сейчас работаю, поэтому я подумал, что я мог бы также опубликовать это здесь. Надеюсь, это кому-нибудь поможет. Я создал образец клиента и службы, которые я использовал, чтобы опробовать некоторые из этих идей. Я убрал его и добавил в 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 создается файл 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:msdn.microsoft.com service station WCF").
Привязки WCF в глубине - очень полезная информация о настройке привязок
Расширение WCF с помощью пользовательского поведения - лучший источник информации о точках интеграции диспетчера, который я нашел, и он содержит несколько действительно полезных диаграмм, которые иллюстрируют все точки интеграции и их местонахождение в порядке обработки.
Образцы Microsoft WCF. Здесь много чего не очень хорошо документировано. Я нашел чтение исходного кода для некоторых из них очень поучительным.
Для пользовательского формата сообщения вам нужен Custom MessageEncoder. MSDN содержит пример того, как создать собственный кодировщик. Если вы используете Reflector, вы найдете несколько реализаций кодировщика, чтобы вы могли научиться его писать.
Вы также можете проверить, что произойдет, если вы попытаетесь использовать TextMessageEncoder с MessageVersion.None (я никогда не пробовал).