Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# WCF Global Namespaces - Royal Mail

Tags:

c#

soap

I've got a WCF SOAP client which is generating a request. This is being refused by the server as an invalid request. I've traced it down to namespaces using SOAPUI but cannot figure out how I can get the client to produce the required result.

The client was generated as a web service reference from the wsdl and is producing the following SOAP message;

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header></s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <createShipmentRequest xmlns="http://www.royalmailgroup.com/api/ship/V2">
      <integrationHeader>
        <dateTime xmlns="http://www.royalmailgroup.com/integration/core/V1">2015-07-23</dateTime>
        <version xmlns="http://www.royalmailgroup.com/integration/core/V1">2</version>
        <identification xmlns="http://www.royalmailgroup.com/integration/core/V1">
          <applicationId>some random number</applicationId>
          <transactionId>some reference number</transactionId>
        </identification>
      </integrationHeader>
    </createShipmentRequest>
  </s:Body>
</s:Envelope>

As you can see the namespaces are being outputted on the individual elements...

The working example I have has the namespaces defined in the SOAP Envelope;

<s:Envelope xmlns:v2="http://www.royalmailgroup.com/api/ship/V2" xmlns:v1="http://www.royalmailgroup.com/integration/core/V1" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <s:Header></s:Header>
  <s:Body>
    <v2:createShipmentRequest>
      <v2:integrationHeader>
        <v1:dateTime>2015-07-23</v1:dateTime>
        <v1:version>2</v1:version>
        <v1:identification>
          <v1:applicationId>some random number</v1:applicationId>
          <v1:transactionId>some reference number</v1:transactionId>
        </v1:identification>
      </v2:integrationHeader>
    </v2:createShipmentRequest>
  </s:Body>
</s:Envelope>

From what I understand this should not make a difference but the sever simply rejects the request. After modifying my generated request in SOAPUI it is defiantly this causing the problem, so how can I move the two namespace definitions v1 & v2 to the SOAP Envelope and then have the correct elements use the correct prefix?

My client is initiated using the following function;

private shippingAPIPortTypeClient GetProxy() {

  BasicHttpBinding myBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
  myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;

  shippingClient = new shippingAPIPortTypeClient(myBinding, new EndpointAddress(new Uri(shippingClientSandboxEndpoint), EndpointIdentity.CreateDnsIdentity("api.royalmail.com"), new AddressHeaderCollection()));
  shippingClient.ClientCredentials.ClientCertificate.Certificate = certificate;

  return shippingClient;
}
like image 929
Ashley Swatton Avatar asked Jul 23 '15 18:07

Ashley Swatton


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr. Stroustroupe.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

Is C programming hard?

C is more difficult to learn than JavaScript, but it's a valuable skill to have because most programming languages are actually implemented in C. This is because C is a “machine-level” language. So learning it will teach you how a computer works and will actually make learning new languages in the future easier.


1 Answers

So it turns out I needed to create a custom MessageFormatter and attach it as a behaviour to the client operations.

For anybody else needing to do this you need 3 files;

Firstly you create a custom message class which implements Message. Here in the OnWriteStartEnvelope method you add/define the namespaces you want in the Envelope.

class RoyalMailMessage: Message {
  private readonly Message message;

  public RoyalMailMessage(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("s", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
    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");

  }
}

Then you create a custom Class which implements IClientMessageFormatter. This makes use of the Message class we defined above for outgoing requests made by the client;

public class RoyalMailMessageFormatter: IClientMessageFormatter {
  private readonly IClientMessageFormatter formatter;

  public RoyalMailMessageFormatter(IClientMessageFormatter formatter) {
    this.formatter = formatter;
  }

  public object DeserializeReply(Message message, object[] parameters) {
    return this.formatter.DeserializeReply(message, parameters);
  }

  public Message SerializeRequest(MessageVersion messageVersion, object[] parameters) {
    var message = this.formatter.SerializeRequest(messageVersion, parameters);
    return new RoyalMailMessage(message);
  }
}

We then need to create a custom class which implements IOperationBehavior. This is needed so we can attatch the custom message formatter to the service operations as a behaviour;

class RoyalMailIEndpointBehavior: IOperationBehavior {

  public RoyalMailIEndpointBehavior() {}

  public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy) {
    IClientMessageFormatter currentFormatter = proxy.Formatter;
    proxy.Formatter = new RoyalMailMessageFormatter(currentFormatter);
  }

  public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) {

  }

  public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) {

  }

  public void Validate(OperationDescription operationDescription) {

  }

}

Finally, we need to add the custom IOperation behaviour to all the service operations generated by WCF;

private shippingAPIPortTypeClient GetProxy() {

  BasicHttpBinding myBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
  myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;

  shippingClient = new shippingAPIPortTypeClient(myBinding, new EndpointAddress(new Uri(shippingClientSandboxEndpoint), EndpointIdentity.CreateDnsIdentity("api.royalmail.com"), new AddressHeaderCollection()));
  shippingClient.ClientCredentials.ClientCertificate.Certificate = certificate;

  foreach(OperationDescription od in shippingClient.Endpoint.Contract.Operations) {
    od.Behaviors.Add(new RoyalMailIEndpointBehavior());
  }
  return shippingClient;
}

The namespaces should now be in the SOAP Envelope and the elements all use the correct prefix giving us something like;

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 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></s:Header>
  <s:Body>
    <v2:createShipmentRequest>
      <v2:integrationHeader>
        <v1:dateTime>2015-07-23T20:37:07.937+01:00</v1:dateTime>
        <v1:version>2</v1:version>
        <v1:identification>
          <v1:applicationId>SOME RANDOM ID</v1:applicationId>
          <v1:transactionId>SOME RANDOM ID</v1:transactionId>
        </v1:identification>
      </v2:integrationHeader>
    </v2:createShipmentRequest>
  </s:Body>
</s:Envelope>
like image 153
Ashley Swatton Avatar answered Sep 30 '22 16:09

Ashley Swatton