Update 26/March/20 - In the intervening years Azure APIM has come a long way - it's now quite common to see customers of APIM facading SOAP webservices - using both Liquid and XSLT to transform legacy webservices to a more modern RESTful experience.
Please do checkout the Azure APIM docs.
***
At the time of writing:
I've been looking at the excellent API-Management feature of Azure .. I understand this functionality has come about through an acquisition (Apiphany).
I think Azure APIM could offer some great benefits.
After becoming familiar with the product through the documentation I was still a little confused about how or in fact whether a WCF-SOAP service was supported as the back-end service. The APIM documentation focuses very much on a REST based example back-end service.
(Note: I assume the readers familiarity with APIM and a previous walk through of the documented examples).
To add to my confusion one of the feedback comments for the APIM product appeared to suggest SOAP was not in fact YET supported: feedback-for-api-management-suggestions
However logic suggests it should work, a SOAP envelope submission over HTTP is via a POST, there are really only three prerequisites
1. Valid SOAP envelope XML which contains an XML body which the target Web service will be able to de-serialize.
2. A matching SOAPAction request header
3. A Content-Type header of "text/xml"
One further consideration is how the consumer obtains the contract for the service. It's an implementation detail, but it's possible to define an http and/or https endpoint from which the consumer of the service can acquire the service definition in the form of a WSDL. From .net v4.5 onward a "singleWSDL" option is supported which I believe to be generally preferable and better for interop.
There were a couple of goals:
1. Firstly I wanted to look at options around surfacing some existing internally facing WCF implemented basic-http (SOAP) services for the future
2. I wanted policy and the end user experience to centre around both endpoint and operation.
In my test I published a simple test WCF service to a public facing server, I didn't add security to this service - wishing to keep it simple initially - but the service was restricted to TLS from the front end load balancer out. So any test would be over https.
The URL for my test back-end service was something like this
https://ourdomain.com/helloworld/service1.svc?singlewsdl
The service had two methods:
[ServiceContract] public interface IService1 { [OperationContract] string HelloWorldOperation1(string value1, int value2, bool value3); [OperationContract] CompositeType HelloWorldOperation2(CompositeType composite); }
(Composite type wrapped the same basic types passed in operation 1 as params)
After creating an API instance on Azure within our account I added some users, a product and a new API!
1. Publisher Portal, API Management, APIs: Settings
The settings of the API were very basic - I did try and import the WSDL definition from file, out of curiosity and after playing with the encoding to get that right (it didn't like the initial file we copied out of Visual Studio). However quickly got an error around the WSDL not being understood.
It could have been me, so it might work, I didn't take more than a couple of minutes trying. Certainly at the time of writing WADL and SWAGGER were the two radio option buttons to describe the definition contents type.
Web API Name = "Hello World"
Description =
Connection = Directly (in a real scenario I'd explore VPN between APIM and back-end, which is beyond this post)
Web Service URL = https://ourdomain.com/helloworld/service1.svc
Web API URL Suffix = service1Endpoint
Https = [ticked]
This was the resulting Web API URL
https://[ourazuredomain]helloworld.azure-api.net/service1Endpoint
2. Publisher Portal, API Management, APIs: Operations
Here's the interesting bit, APIM really doesn't do much until you add at least one operation and doesn't seem to surface at all right now if you publish an API without any (which is logical).
This is the bit I got stuck with, what to put in the operation section for a SOAP endpoint method?
What I began by doing was adding a single POST method to cover all endpoint methods, thus:
Http Verb = "POST" (I want to post my soap envelope)
Url Template = "/"
Rewrite URL tempate = ""
Display Name = "All operations"
But, I want to be in a position to add a POST operation to the API for each endpoint method, and this endpoint encapsulates ALL the methods, it's the SOAPAction at the back-end service which determines which method gets invoked on the endpoint and then the request XML within the SOAP body must match!
So I went back to the Web Service URL from step 1 and changed it to this:
Web Service URL = https://ourdomain.com/helloworld
(note I've lopped off the "/service1.svc" endpoint part)
Then I went back and edited my API method to look like this:-
Http Verb = "POST"
Url Template = "/operation1"
Rewrite URL tempate = "/service1.svc"
Display Name = "Operation 1"
The key point here is that we now specify a URL segment to represent the method but we ALWAYS rewrite it to the service endpoint.
The reason for doing this is so that I can specify distinct operations.
But now I've got a further problem - there's nothing that can enforce the fact that I need the right payload for this operation to go to the right method....Yet...
Next I added an additional operation in for the second web service method, identical to that above except for the URL Template ("/operation2") and the Display name "Operation 2".
Finally I added a third method, a GET this time, to allow my consumers a chance to see the WSDL
Http Verb = "GET"
Url Template = "/GetWsdl"
Rewrite URL tempate = "/service1.svc?wsdl"
Display Name = "Get WSDL"
(Note: My test server was net4.0 otherwise it'd have been ?singleWsdl)
The important point is that my GET to the exposed URL segment "/GetWsdl" will always return the wsdl for this endpoint as it's re-rewritten as "/service1.svc?wsdl". I.e. with the wsdl URL query param.
Finally, one more step before I publish; need a way to associate the SOAP header to the correct operation - and one more thing I need to ensure I'm sending and receiving with a content type of "text/xml". And not "text/plain".
3. Publisher Portal, API Management, Policies: Policy Scope
I added a policy using the wizard for each of the endpoint operations defined in the steps above, the XML ended up looking like this:
GetWsdl
<policies> <inbound> <set-header exists-action="override" name="content-type"> <value>text/xml</value> </set-header> <base></base> <rewrite-uri template="/service1.svc?wsdl"> </rewrite-uri></inbound> <outbound> <set-header exists-action="override" name="content-type"> <value>text/xml</value> </set-header> <base></base> </outbound> </policies>
Operation1
<policies> <inbound> <set-header exists-action="override" name="content-type"> <value>text/xml</value> </set-header> <set-header exists-action="override" name="SOAPAction"> <value>http://tempuri.org/IService1/HelloWorldOperation1</value> </set-header> <base></base> <rewrite-uri template="/service1.svc"> </rewrite-uri></inbound> <outbound> <set-header exists-action="override" name="content-type"> <value>text/xml</value> </set-header> <base></base> </outbound> </policies>
Operation2
<policies> <inbound> <base></base> <set-header exists-action="override" name="content-type"> <value>text/xml</value> </set-header> <set-header exists-action="override" name="SOAPAction"> <value>http://tempuri.org/IService1/HelloWorldOperation2</value> </set-header> <rewrite-uri template="/service1.svc"> </rewrite-uri></inbound> <outbound> <set-header exists-action="override" name="content-type"> <value>text/xml</value> </set-header> <base></base> </outbound> </policies>
Given the fact the policies are hierarchical I could have optimized this by moving the content type header up a level.
The next most important part is the SOAPAction for the POST operations.
This ensures the right action goes with the right operation. There's nothing stopping the end user from sending a different action and request content but note the policy is set to OVERRIDE whatever the users sending which means they'd get an error if they sent the wrong payload to the wrong URL.
Clearly there's one quite big problem with all of this: When the user imports the WSDL into a .net application it's going to want to do so under a single endpoint - whereas APIM will want to a distinct URL per method. So there's a mismatch.
One approach could be to define the operations with the differentiator as a query string argument (of the SOAPAction name) instead of the URL segment, here's how operation1 might look like
Http Verb = "POST"
Url Template = "/?op=HelloWorldOperation1"
Rewrite URL tempate = "/service1.svc"
Display Name = "Operation 1"
Later on that would be called, along with the subscription key instead of:
https://[ourazuresomain]helloworld.azure-api.net/service1Endpoint/operation1/?subscription-key=[ourkey]
It would be
https://[ourazuredomain]helloworld.azure-api.net/service1Endpoint/?op=HelloWorldOperation1&subscription-key=[ourkey]
Now the caller only has to manage the one URL in their web.config (let's assume they're using .net for now).
However this raises yet another point, which is how does the customer ensure that the subscription key gets added in the first place so the APIM façade allows the call through...
We could ditch the subscription key, it's optional within APIM, but it's one of the things I'd want to use.
As it turns out, the subscription key doesn't have to be passed as a query string param, it can instead be passed via a request header.
The subscription key request header is "ocp-apim-subscription-key".
This might make more sense for a consuming customer, who could configure a set outbound header against their caller/client app to be attached for outgoing requests to our API.
In Summary
The exercise has proved we can relatively easily façade a wcf http SOAP service through APIM and go onto craft a neat developer portal experience, but, it doesn't feel quite right yet. The APIM is currently REST centric and requires a unique URL per operation. This is at odds with SOAP which is a single URL and utilises a SOAP Action to route to the correct operation for the contract the endpoint URL represents.
The steps detailed above put the onus on the APIM façade consumer to modify the outbound call from client to service [façade] and that doesn't feel right. Also posted a comment here: feedback-azure-api-management-suggestions-soap-support