Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Publishing a SOAP 1.2 WS not using action parameter

0.00/5 (No votes)
6 Aug 2013CPOL4 min read 40.4K   257  
This article is about publishing a SOAP 1.2 web service that is not using any action parameter.

Introduction

This article is about publishing a SOAP 1.2 web service that is not using any action parameter.

Background

A few weeks ago, I've been asked to publish a web service. The specification of this web service (WSDL) has been defined by our customer. They already have written the consumer of the web service.

The consumer designed by our customer rely on a homemade middleware. The particularity of this middleware is that it doesn't publish the SOAP action beeing called in HTTP headers. Althrough it is not mandatory (www.w3.org/TR/2003/REC-soap12-part2-20030624/#ietf-action), it's necessary for .NET to route the message.

Moreover, this middleware doesn't use the WS-Addressing extension available in SOAP 1.2.

In fact, it is possible to use a default action ([System.ServiceModel.OperationContractAttribute(Action="*", ReplyAction="*")] in the ServiceContract), but all the magic evaporates, and many manipulation are necessary in the webservice code.

This is the reason why I've looked for another solution : proxify requests.

Sample

With the view to illustrate this example, let's suppose that we aim at publish a simple Calculator web service. Operations are Add, Substract, and Multiply. By connecting to the web service using soapUI, we are able to test the add operation successfully.

Image 1

SOAP Action and action parameter

First of all, it is important to clarify locations where action could be defined. 2 places exists :

  • inside SOAP header ;
  • in the content-type inside HTTP headers.

SOAP Action inside SOAP header

By default, C# rely primarily on the information contained into SOAP headers. By the way, the content of the SOAP message is the following :

XML
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" 
           xmlns:cal="http://www.distorsion.fr/calculator">
   <soap:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
   <wsa:Action>http://www.distorsion.fr/calculator/Add</wsa:Action></soap:Header>
   <soap:Body>
      <cal:Add>
         <!--Optional:-->
         <cal:p_A>1</cal:p_A>
         <!--Optional:-->
         <cal:p_B>1</cal:p_B>
      </cal:Add>
   </soap:Body>
</soap:Envelope>

Even if the action is not defined into HTTP headers, the routing works well and the web service answer correctely. To disable WS-Addressing on a WsHttpBinding, you'll need to define a CustomBinding :

C#
WSHttpBinding l_Binding = new WSHttpBinding(SecurityMode.None);
l_Binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
CustomBinding l_CustomBinding = new CustomBinding(l_Binding);
MessageEncodingBindingElement l_EncodingElement = 
     l_CustomBinding.Elements.Find<MessageEncodingBindingElement>();
l_EncodingElement.MessageVersion = MessageVersion.Soap12;

If you want to verify that WS-Addressing is enabled, you should fing UsingAddressing into WSDL :

XML
<wsdl:definitions name="CalculatorService" 
         targetNamespace="http://www.distorsion.fr/calculator">
   <wsp:Policy wsu:Id="CalculatorService_policy">
      <wsp:ExactlyOne>
         <wsp:All>
            <wsaw:UsingAddressing/>
         </wsp:All>
      </wsp:ExactlyOne>
   </wsp:Policy>
... 

Action parameter in the content-type inside HTTP headers

Even if the action is defined into SOAP header or not, SOAP allow client to define action into HTTP headers.

POST http://localhost:44305/calculator.svc HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/soap+xml;charset=UTF-8;action="http://www.distorsion.fr/calculator/Add"
Content-Length: 337
Host: localhost:44305
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

Action parameter is defined in the Content-type header. It is recommended that actions, if they are both defined, are the same. Using Action parameter into HTTP headers, when UsingAddressing is not defined is not mandatory, from the protocol point of view. But, in fact, .NET Framework relies on action parameter if WS-Addressing is disabled.

Action parameter

Having a strong look at the HTTP exchange, we can see that the following headers are sent by soapUI :

POST http://localhost:44305/calculator.svc HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/soap+xml;charset=UTF-8;action="http://www.distorsion.fr/calculator/Add"
Content-Length: 337
Host: localhost:44305
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

The content-type headers contains the fqdn SOAP action. In soapUI, by cliquing on the request and setting Skip SOAP Action to true in properties, the action won't be longer sent in HTTP Content-type header but the web service will throw a SOAP Fault whose subcode will be ActionNotSupported and reason :

The message with Action "" cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g., Message, Transport, None).

HttpProxy

To avoid this fault, the first solution is to use a default action ([System.ServiceModel.OperationContractAttribute(Action="*", ReplyAction="*")] in the ServiceContract). But, doing this, the simplicity of SOAP web services is just gone, and you'll need to manipulate stream in a very inconvenient way. This is the reason why I've chosen to use a proxy.

The proxy will receive the request, and based upon the body, will inject into headers the action parameter. The hypothesis is that the action will be the child tag of the Body tag.

XML
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" 
         xmlns:cal="http://www.distorsion.fr/calculator">
   <soap:Header/>
   <soap:Body>
      <cal:Add>
         <!--Optional:-->
         <cal:p_A>4</cal:p_A>
         <!--Optional:-->
         <cal:p_B>2</cal:p_B>
      </cal:Add>
   </soap:Body>
</soap:Envelope>

On the previous sample, the Add tag is the child of the Body tag.

To be able to listen to requests, HttpProxy will have to listen on a specific port, different from the webservice's port. This is the tricky part : the wsdl file will have to be modified so that endpoints point to the proxy listen port instead of the webservice endpoints.

Using the code

To use HttpProxy, you will first have to pay attention to operation contracts. With the view to simplify proxyfiing, their definitions doesn't contains reference to the service name. For example, in the sample, without explicit definition, the action of operation contract concerning the Add operation would have been : http://www.distorsion.fr/calculator/CalculatorService/Add. By explicitly defining operation contract action to only http://www.distorsion.fr/calculator/CalculatorService/Add, routing is simplified : the action is the combination of the target name space and the action local name, Add.

Moreover, HttpProxy will work, in this very basic version, only if the service endpoint is on the same machine that the proxy.

Once these simple verifications are done, you'll have the possibility to instantiate HttpProxy using the following arguments :

  • p_ListenPort : port that HttpProxy will listen to ;
  • p_Encoding : encoding is important because our proxy will have to inspect content of request to complete Http content-type header ;
  • p_ProxifiedWSName : this is the domain name of the service. For example, I could have published my web service on ws.distorsion.fr. This value could be different from the target name space ;
  • p_ProxifiedWSPort : listen port of the proxified web service.
C++
m_HttpProxy = new HttpProxy(44306, Encoding.UTF8, "localhost", 44305);
m_HttpProxy.Start();

History

  • 06/08/2013: First submission of this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)