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
| SOAP | REST | |
|---|---|---|
| Payload Format | XML | JSON Preferred, can support XML |
| Resource Identifiers | One root URL, Individual Operations go in header – SOAPAction Example: URL: https://<URL>/Students GetAllStudent Operation: (Header) SOAPAction : “http://tempuri.org/IService1/GetAllStudents” | Each resource have individual URI Example: URL: https://<URL>/Students No Header needed to identify Individual operation |
| Request Sample | <?xml version="1.0" encoding="utf-8"?> | { |
| Response Sample | <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> | { |
| HTTP Verbs | Only POST | All verbs supported |
Case study at hand
I have already hosted a bunch of resources in the Azure as listed below 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.
<?xml version="1.0" encoding="UTF-8"?>
<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:Courses>
{%- for item in {{body.courses}} -%}
<a:Course>
<a:CourseId>{{item.courseId}}</a:CourseId>
<a:CourseName>{{item.courseName}}</a:CourseName>
</a:Course>
{%- endfor -%}
</a:Courses>
<a:Gpa>{{body.gpa}}</a:Gpa>
<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>
<choose>
<when condition="@(context.Variables.GetValueOrDefault<string>("operationName")
=="http://tempuri.org/IService1/GetStudentData")">
<set-header name="ReplyAction" exists-action="override">
<value>http://tempuri.org/IService1/GetStudentDataResponse</value>
</set-header>
</when>
</choose>
</outbound>
Note how When condition is used to set the Response Body and Response Header, so that SOAP clients can interpret it accordingly.
The result
See in the SOAP UI screenshot below, where we are calling APIM request which is a SOAP endpoint and it then converts and talks to the REST API and returns the SOAP response back.

You can see exactly the same through the C# SOAP client code and it works perfectly fine.

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.