Ответ 1
Таким образом, ответ на эту проблему состоял в том, чтобы создать пользовательские IClientMessageFormatter
и Message
, а затем переопределить Message.OnWriteStartEnvelope()
, чтобы явно выписать все пространства имен в корневом каталоге Soap. Полученный документ, а затем повторно использует эти пространства имен вместо явного назначения пространств имен дочерних элементов.
Для этого необходимо создать 3 класса:
- Реализация сообщений, которая обрабатывает фактический
OnWriteStartEnvelope()
- IClientMessageFormatter, подключенный к WCF
- FormatMessageAttribute для присоединения к каждому методу клиента
Здесь код для всех трех:
public class RoyalMailCustomMessage : Message
{
private readonly Message message;
public RoyalMailCustomMessage(Message message)
{
this.message = message;
}
public override MessageHeaders Headers
{
get { return this.message.Headers; }
}
public override MessageProperties Properties
{
get { return this.message.Properties; }
}
public override MessageVersion Version
{
get { return this.message.Version; }
}
protected override void OnWriteStartBody(XmlDictionaryWriter writer)
{
writer.WriteStartElement("Body", "http://schemas.xmlsoap.org/soap/envelope/");
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
this.message.WriteBodyContents(writer);
}
protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer)
{
writer.WriteStartElement("soapenv", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
writer.WriteAttributeString("xmlns", "oas", null, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteAttributeString("xmlns", "v2", null, "http://www.royalmailgroup.com/api/ship/V2");
writer.WriteAttributeString("xmlns", "v1", null, "http://www.royalmailgroup.com/integration/core/V1");
writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");
}
}
public class RoyalMailMessageFormatter : IClientMessageFormatter
{
private readonly IClientMessageFormatter formatter;
public RoyalMailMessageFormatter(IClientMessageFormatter formatter)
{
this.formatter = formatter;
}
public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
{
var message = this.formatter.SerializeRequest(messageVersion, parameters);
return new RoyalMailCustomMessage(message);
}
public object DeserializeReply(Message message, object[] parameters)
{
return this.formatter.DeserializeReply(message, parameters);
}
}
[AttributeUsage(AttributeTargets.Method)]
public class RoyalMailFormatMessageAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription,
BindingParameterCollection bindingParameters)
{ }
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
var serializerBehavior = operationDescription.Behaviors.Find<XmlSerializerOperationBehavior>();
if (clientOperation.Formatter == null)
((IOperationBehavior)serializerBehavior).ApplyClientBehavior(operationDescription, clientOperation);
IClientMessageFormatter innerClientFormatter = clientOperation.Formatter;
clientOperation.Formatter = new RoyalMailMessageFormatter(innerClientFormatter);
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{ }
public void Validate(OperationDescription operationDescription) { }
}
Большая часть этого - церемония и шаблонный код. key code штук OnWriteStartEnvelope
, где фактические пространства имен подключены, SerializeRequest
, где форматировщик подключен к конвейеру WCF и ApplyClientBehavior
, где формат сообщения прикреплен к фактической операции.
Чтобы связать это, я добавил атрибут к клиентскому методу в интерфейсе службы - в этом случае в моем сгенерированном клиенте WCF в Reference.cs
.
// CODEGEN: Generating message contract since the operation cancelShipment is neither RPC nor document wrapped.
[System.ServiceModel.OperationContractAttribute(Action="cancelShipment", ReplyAction="*")]
[System.ServiceModel.FaultContractAttribute(typeof(MarvelPress.Workflow.Business.RoyalShippingApi.exceptionDetails), Action="cancelShipment", Name="exceptionDetails")]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(contactMechanism))]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(baseRequest))]
[RoyalMailFormatMessage()]
MarvelPress.Workflow.Business.RoyalShippingApi.cancelShipmentResponse1 cancelShipment(MarvelPress.Workflow.Business.RoyalShippingApi.cancelShipmentRequest1 request);
Сообщения, созданные из WCF, теперь выглядят так, как ожидалось, с пространствами имен, определенными в верхней части документа:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:oas="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:v2="http://www.royalmailgroup.com/api/ship/V2" xmlns:v1="http://www.royalmailgroup.com/integration/core/V1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<s:Header xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<h:Security>...</h:Security>
</s:Header>
<soapenv:Body>
<v2:cancelShipmentRequest>
<v2:integrationHeader>
<v1:dateTime>2016-04-02T01:04:50.4122473Z</v1:dateTime>
<v1:version>2</v1:version>
<v1:identification>
<v1:applicationId>RMG-API-G-01</v1:applicationId>
<v1:transactionId>fshrxevdnc7n</v1:transactionId>
</v1:identification>
</v2:integrationHeader>
<v2:cancelShipments>
<v2:shipmentNumber>TTT001908905GB</v2:shipmentNumber>
</v2:cancelShipments>
</v2:cancelShipmentRequest>
</soapenv:Body>
</soapenv:Envelope>
Для получения дополнительной информации и общего пространства имен, добавляющего форматирование, проверьте мою связанную запись в блоге здесь: http://weblog.west-wind.com/posts/2016/Apr/02/Custom-Message-Formatting-in-WCF-to-add-all-Namespaces-to-the-SOAP-Envelope