Azure API Management – Liquid Template – Escape XML Characters

Requirement:

Create an API that allows creation of customer in backend ERP.

API should be accessible publicly and secured.

Landscape:

  1. ERP is SAP and is located on a on-premise network.
  2. BizTalk is used as middleware to connect to SAP and is also located on-premise. BizTalk doesn’t have a public endpoint available.
  3. Azure API Management is deployed in Azure cloud without direct connection to on-premise network.

Solution:

  1. BizTalk to leverage WCF-BasicHttpRelay to Azure Service Bus to be able to have public endpoint.

2. Azure API management to convert the JSON Request to XML message using Liquid Template.

3. Azure API management to send the request to Azure Service Bus.

4. BizTalk receives the request in XML and send the request to SAP.

Problem:

If the JSON request contains XML reserved characters: & < > ‘ the request is failing in Azure API management.

Solution:

Add the following to the policy:

<!–Replace Reserved XML characters from Request Body–>

<find-and-replace from=”&amp;” to=”&amp;amp;” />

<find-and-replace from=”&gt;” to=”&amp;gt;” />

<find-and-replace from=”&lt;” to=”&amp;lt;” />

<find-and-replace from=”&apos;” to=”&amp;apos;” />

How to setup multi-region Azure API Management using Internal VNET configuration

If you’re looking answer for the following:

  • How to deploy Azure API Management to multiple regions?
  • How to setup Azure API management multi region with internal VNET configuration
  • How to configure Azure API management with Application Gateway

You’re in the right place.

Overview

APIM Setup

Azure components needed:

  1. 2 Traffic manager profiles. 1 for portal, 1 for gateway
  2. Application Gateway + WAF v2 (per region)
  3. Pubic IP Address for Application Gateway (per region)
  4. Certificates for portal and gateway
  5. DNS for portal and gateway
  6. Azure API Management Premium using Internal VNET configuration
  7. Virtual Network (per region)
  8. IP Address ranges (per VNET) this should be properly allocated to avoid conflict when joining this to internal network.

 

Prerequisite:

  1. Provision Azure API Management Premium instance.

Steps (below needs to be repeated for every region):

  1. Create VNET with 2 subnets. 1 for Application Gateway (AG) and 1 for APIM.
  2. Create public IP address (static)
  3. Follow instructions in using API management in internal configuration with application gateway (https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-integrate-internal-vnet-appgateway)
  4. Configure the APIM Gateway Traffic manager to have an endpoint pointed the static public IP address of application gateway.

 

How traffic works with reference to Andrews post:

APIM-Traffic

View at Medium.com

Azure API Management Policy – Asynchronous API as Synchronous API

Below is the link to reference/examples of using Azure API management policy

There’s an example there on how to Mask Asynchronous calls as Synchronous API however for my requirement it’s not enough.

The target API behaves as follow:

  1. All operations are POST and asynchronous.
  2. To get the results of any operation a second API needs to be called and it’s expecting the transaction id.
  3. API expects a payload in command manner (args parameter) instead of proper JSON name/value pair.

Requirement: API should return the results in synchronous manner.

Solution:

API Callout Policy in API Management.

Steps: 

  1. Create a GET operation and via policy rewrite the operation to POST
  2. Create a JSON transformation logic in the inbound policy and execute the backend API.
  3. Create a JSON transformation logic in outbound policy and add a retry policy to execute the second API  that returns the result passing the transaction id from #2
  4. Return the results from second API.

Policy Code:

<policies>
<inbound>
<base />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<!-- Save authorization data to variable, to be used in outbound status request -->
<set-variable name="authorization" value="@(context.Request.Headers.GetValueOrDefault("Authorization"))" />
<!-- 
Construct the payload
-->
<set-body>@{
var paramFromRequest = Uri.UnescapeDataString(context.Request.OriginalUrl.Query.GetValueOrDefault("paramFromRequest"));
JObject transBody = new JObject();
transBody.Add("source", 
     new JObject
     {
         {"someproperty", "fixvalue"},
         {"someproperty2", "fixvalue2"},
     });

//Add all json properties as arg
transBody.Add("args", JToken.FromObject(new[] 
{ 
      paramFromRequest
}));
return transBody.ToString();
}</set-body>
<trace source="debug">@(context.Request.Body.As<string>(true))</trace>
<!--Remove additional query string -->
<set-query-parameter name="paramFromRequest" exists-action="delete" />
<set-method>POST</set-method>
<rewrite-uri template="{{FIRSTBACKENDAPI_URL}}" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<!-- Get the Key from response -->
<set-variable name="jsonPayload" value="@{ 
JObject inBody = context.Response.Body.As<JObject>();
var transactionId= inBody["transactionId"].ToString(); 
string jsonString = "{\"transactionId\":\"_transactionId\" }";
var json = jsonString.Replace("_transactionId", transactionId);
return json;
}" />
<retry condition="@( Convert.ToBoolean(context.Variables.GetValueOrDefault<JObject>("status")["{{PROPERTYNAME IN JSON OBJECT RESPONSE}}"]))" count="10" interval="1">
<!-- Call Status API to get the results-->
<send-request mode="new" response-variable-name="var" ignore-error="false">
<set-url>@{ 
var url = context.Api.ServiceUrl+ "{{SECONDBACKENDAPI_URL}}";
return url;
}</set-url>
<set-method>POST</set-method>
<set-header name="Authorization" exists-action="override">
<value>@(context.Variables.GetValueOrDefault<string>("authorization"))</value>
</set-header>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body template="none">@(context.Variables.GetValueOrDefault<string>("jsonPayload"))</set-body>
</send-request>
<set-variable name="results" value="@(((IResponse)context.Variables["var"]).Body.As<JObject>())" />
</retry>
<return-response>
<set-body template="none">@((context.Variables.GetValueOrDefault<JObject>("results")["{{PROPERTYNAME IN JSON OBJECT THAT CONTAINS THE RESULT}}"].ToString()))</set-body>
</return-response>
</outbound>
<on-error>
<base />
</on-error>
</policies>