Expose SOAP service in Azure API Management and store data in Blob

Expose SOAP service in Azure API Management and store data in Blob

Table of Contents

1 Objective
2 Create SOAP API
3 Policy
4 Request
5 Blob

1 Objective

An external system has to send data but it's only capable to send SOAP messages. REST does not work for this system.
API Management has to expose an endpoint to receive SOAP messages.
For further processing, the entire SOAP message has to be stored in Azure Blob.

2 Create SOAP API

Create a new API in API Management by selecting WSDL.
Microsoft: Import SOAP API

APIM-NEW-WSDL.png

Provide a link to WSDL-Document or upload a WSDL-Document. In my case, I uploaded a document:

APIM-SOAP-WSDL.png

I used the data contract for my previous post:
Transforming data from JSON to XML and XML to JSON with C#

DataContract:

[DataContract(Name = "person")]
public class Person
{
    [DataMember(Name ="firstname")] 
    public string FirstName { get; set; }

    [DataMember(Name ="lastname")]
    public string LastName { get; set; }

    [DataMember(Name ="gender"), XmlAttribute]
    public string Gender { get; set; }

    [DataMember(Name ="age")]
    public int Age { get; set; }

}

The service itself contains a store method:

ServiceContract:

[ServiceContract]
public interface IPerson
{
    [OperationContract]
    void Store(PersonModel personModel);
}

By running the service on my machine, I saved the WSDL to the local disc.

3 Policy

The inbound policy contains a put request to Blob. Most of the code is about authorization. Please find more details about it in the Microsoft documentation.

The data will be stored in the container "soap".

Named Values:

BLOB-URL: mmdatatransformation.blob.core.windows.net

BLOB-KEY: secret


<inbound>
    <base />
    <!-- ########## put to storage ########## -->
    <set-variable name="container" value="soap" />
    <set-variable name="fileName" value="@{return Guid.NewGuid().ToString() + ".xml";}" />
    <set-variable name="resource" value="@{
                        string prefix = "/" + context.Variables.GetValueOrDefault<string>("container") + "/";
                        string fileName = context.Variables.GetValueOrDefault<string>("fileName");
                        return prefix + fileName;
                        }" />
    <set-variable name="storageUrl" value="{{BLOB-URL}}" />
    <set-variable name="storageKey" value="{{BLOB-KEY}}" />
    <set-variable name="storageAccountName" value="@(context.Variables.GetValueOrDefault<string>("storageUrl").Split('.')[0].Split('/')[2])" />
    <set-variable name="blobUrl" value="@(context.Variables.GetValueOrDefault<string>("storageUrl") + context.Variables.GetValueOrDefault<string>("resource"))" />
    <set-variable name="date" value="@(DateTime.UtcNow.ToString("R"))" />
    <set-variable name="version" value="2018-03-28" />
    <set-backend-service base-url="@(context.Variables.GetValueOrDefault<string>("storageUrl") + "/" + context.Variables.GetValueOrDefault<string>("container"))" />
    <set-method>PUT</set-method>
    <set-header name="x-ms-date" exists-action="override">
        <value>@(context.Variables.GetValueOrDefault<string>("date") )</value>
    </set-header>
    <set-header name="x-ms-version" exists-action="override">
        <value>@(context.Variables.GetValueOrDefault<string>("version"))</value>
    </set-header>
    <set-header name="x-ms-blob-type" exists-action="override">
        <value>BlockBlob</value>
    </set-header>
    <set-header name="Content-Type" exists-action="override">
        <value>application/xml</value>
    </set-header>
    <set-header name="Authorization" exists-action="override">
        <value>@{
            string body = context.Request.Body.As<string>(preserveContent: true);
            string contentType = "application/xml";
            string contentLength = context.Request.Headers["Content-Length"][0];
            var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(context.Variables.GetValueOrDefault<string>("storageKey")) };
            var payLoad = string.Format("{0}\n\n\n{1}\n\n{2}\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:{3}\nx-ms-version:{4}\n{5}", 
                "PUT", 
                contentLength,
                contentType,
                context.Variables.GetValueOrDefault<string>("date"),
                context.Variables.GetValueOrDefault<string>("version"),
                "/" + context.Variables.GetValueOrDefault<string>("storageAccountName") + context.Variables.GetValueOrDefault<string>("resource"));
            return "SharedKey "+ context.Variables.GetValueOrDefault<string>("storageAccountName") + ":" + Convert.ToBase64String(hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad)));
        }</value>
    </set-header>
    <set-body>@( context.Request.Body.As<string>(true) )</set-body>
    <rewrite-uri template="@("/" + context.Variables.GetValueOrDefault<string>("fileName"))" copy-unmatched-params="true" />
</inbound>

4 Request

My API contains one operation: Store

apim_SOAP.png

With Postman a SOAP header has to be added:
SOAPAction: tempuri.org/IPerson/Store

POST mm-sample.azure-api.net/person

Request Body:

<?xml version="1.0" encoding="utf-8"?>
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
      <Body>
        <Store xmlns="http://tempuri.org/">
          <person>
            <age>1</age>
            <firstname>firstname1</firstname>
            <gender>gender1</gender>
            <lastname>lastname1</lastname>
          </person>
        </Store>
      </Body>
</Envelope>

Postman: postman-soap-request.png

5 Blob

Finally, the SOAP data is stored in the blob and ready for further processing:

soap_blob.png