REST to SOAP using Azure API Management
How to expose legacy SOAP based services to modern REST base clients using Azure API Management policies and Liquid Templates.
I have seen several use-cases where you have to expose your legacy SOAP based services to modern REST base clients. Azure API Management really helps a lot in doing the magic and transforming the SOAP services to REST and it does out of the box.
But I have also seen a reverse use-case, it may come as a surprise but we had a scenario where we had to convert the REST API to be consumed by the SOAP clients. That's where Azure's Liquid Templates shine, but the issue is to be able to do that, we first need to know how both protocols differs so that we can write the policies to convert between each other.
I will demonstrate how we can do that, but first let's see what's the main difference that we need to understand to be able to do the conversion.
Fundamental difference between REST and SOAP
| Feature | SOAP | REST |
|---|---|---|
| Payload Format | XML | JSON Preferred, can support XML |
| Resource Identifiers | One root URL, Individual Operations go in header – SOAPAction | Each resource have individual URI |
| HTTP Verbs | Only POST | All verbs supported |
Request Sample (SOAP):
<?xml version="1.0" encoding="utf-8"?>
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<GetStudentData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://tempuri.org/">
<input>
<StudentId xmlns="http://schemas.datacontract.org/2004/07/WcfPocForApiM">1</StudentId>
<StudentName xmlns="http://schemas.datacontract.org/2004/07/WcfPocForApiM">Tahir</StudentName>
</input>
</GetStudentData>
</Body>
</Envelope>
Request Sample (REST):
{
"studentId": 1,
"studentName": "Tom"
}
Case study at hand
I have already hosted a bunch of resources in the Azure and I will just put the screenshots below to show you what I have already prepared and then we will have a look at the Azure API M Policies used to do the conversion and we will test that through a Client web app as well as Soap UI.
I have hosted the below API in Azure App service as you can see in the POST request through Postman. This is the request we will be converting to SOAP.
API-M Policy
As you can see in the table above that the SOAP UI has only one URI and each individual operation goes in the Request header as the "SOAPAction", which means we need conditional policies to send the request to appropriate backend REST API resource, it also means that we need to create the REST payload on the fly. Lets see the inbound policy below.
<inbound>
<base />
<set-variable name="operationName"
value="@(context.Response.Headers.GetValueOrDefault("SOAPAction",""))" />
<choose>
<when condition="@(context.Variables.GetValueOrDefault<string>("operationName")
=="http://tempuri.org/IService1/GetStudentData")">
<set-backend-service base-url="https://restwebapi120200412190526.azurewebsites.net" />
<set-body template="liquid">
{
"studentId" : "{{body.envelope.body.GetStudentData.input.StudentId}}",
"studentName" : "{{body.envelope.body.GetStudentData.input.StudentName}}"
}
</set-body>
</when>
</choose>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
</inbound>
Note how we have used <choose> and <when> to change the backend URL and Request REST Payload based on the header condition.
Now let's have a look at the Outbound policy.
<outbound>
<base />
<set-body template="liquid">
<choose>
<when condition="@(context.Variables.GetValueOrDefault<string>("operationName")
=="http://tempuri.org/IService1/GetStudentData")">
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetStudentDataResponse xmlns="http://tempuri.org/">
<GetStudentDataResult xmlns:a="http://schemas.datacontract.org/2004/07/WcfPocForApiM" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:ClassName>{{body.className}}</a:ClassName>
<a:CollegeName>{{body.collegeName}}</a:CollegeName>
<a:StudentId>{{body.studentId}}</a:StudentId>
<a:StudentName>{{body.studentName}}</a:StudentName>
</GetStudentDataResult>
</GetStudentDataResponse>
</s:Body>
</s:Envelope>
</when>
</choose>
</set-body>
<set-header name="Content-Type" exists-action="override">
<value>text/xml</value>
</set-header>
</outbound>
Note how When condition is used to set the Response Body and Response Header, so that SOAP clients can interpret it accordingly.
Key Take Aways
Although this approach overall solves your problem and you can convert the REST API to be consumed by the SOAP clients but I highly discourage such conversion because of the following reasons and you should only do such conversion when there is no other way out.
-
In this case you don't have WSDL URLs which means any change in the formats of the request or response will be very tedious to maintain as "Update service reference" won't work.
-
You will have to manually change the auto-generated code of the ServiceReferences to point to the APIM URL.
-
Since there can be only one URL and the operations are segregated by only headers, there will be only one API operation in the API Management which will become point of contention.
-
It will become very hard to maintain as you are dealing with conversion of every single response and request in a single Policy.