Клиент WCF: принудительное глобальное пространство имен

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

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

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

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <h:Security xmlns:h="http://docs.oasis-open.org/wss/2004/01/oasis-
          ...
    </h:Security>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <cancelShipmentRequest xmlns="http://www.royalmailgroup.com/api/ship/V2">
      <integrationHeader>
        <dateTime xmlns="http://www.royalmailgroup.com/integration/core/V1">2016-03-26T01:44:37.0493801Z</dateTime>
        <version xmlns="http://www.royalmailgroup.com/integration/core/V1">2</version>
        <identification xmlns="http://www.royalmailgroup.com/integration/core/V1">
          <applicationId>RMG-API-G-01</applicationId>
          <transactionId>ozhckwej6sxg</transactionId>
        </identification>
      </integrationHeader>
      <cancelShipments>
        <shipmentNumber>TTT001908905GB</shipmentNumber>
      </cancelShipments>
    </cancelShipmentRequest>
  </s:Body>
</s:Envelope>

который не работает.

Использование следующего SOAP-конверта (вручную в SoapUI) действительно работает:

<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">
  <soapenv:Header>
    <h:Security xmlns:h="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       ...
    </h:Security>
  </soapenv:Header>
  <soapenv:Body>
    <v2:cancelShipmentRequest>
      <v2:integrationHeader>
        <v1:dateTime>2016-03-02T14:55:00Z</v1:dateTime>
        <v1:version>2</v1:version>
        <v1:identification>
          <v1:applicationId>RMG-API-G-01</v1:applicationId>
          <v1:transactionId>wftdaife96gv</v1:transactionId>
        </v1:identification>
      </v2:integrationHeader>
      <v2:cancelShipments>
        <v2:shipmentNumber>TTT001908905GB</v2:shipmentNumber>
      </v2:cancelShipments>
    </v2:cancelShipmentRequest>
  </soapenv:Body>
</soapenv:Envelope>

Разница между ними заключается в том, что пространства имен v1 и v2 объявляются глобально в верхней части документа, и во втором документе нет объявлений пространства имен.

Возможно, мне что-то не хватает, но для меня XML-код, созданный WCF, выглядит действительным и представляет одно и то же состояние документа в терминах пространства имен.

Единственное различие, которое я могу сказать, это то, как объявляются пространства имен. И хотя версия WCF, по-видимому, является действительной и создает одно и то же пространство имен, служба жалуется на недопустимые ссылки пространства имен.

Сбой проверки схемы: Ошибка проверки схемы: ошибка проверки схемы: элемент "xmlns": этот элемент не ожидается. Ожидается ({http://www.royalmailgroup.com/api/ship/V2} integrationHeader).

Вопрос в том, что лучший способ заставить WCF добавлять ссылки пространства имен наверху вместо встроенного? Единственный способ, который я нашел до сих пор, - это использовать инспектор сообщений и явно переписать это сообщение, но если я просмотрю все, что мог, просто вручную создаю сообщения.

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

Ответ 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