Altering a JSON Response with Azure API Management Portal Policies
One of my clients has started using Azure API Management (APIM) on top of their ASP.NET Web API endpoint. This worked fine for just about everything, with very few changes, until we started returning paged result models. Suddenly, our clients were making requests to the internal URL, and not the APIM URL, and things started blowing up. There’s a happy ending to this story though, as fixing the issue was easy, thanks to APIM’s built-in Policies.
[more]
NOTE: If you aren’t familiar with Azure API Management, here’s an overview to get you started.
All of our API endpoints allow you to page the results using OData. All you have to do is specify $top and $skip, like so:
https://our-api.azure-api.net/achme/api/v1/Widgets?$top=1
Our endpoints return JSON data, and because of OData, the responses are shaped like this:
{
"result": [
{
"name": "Widget 01",
"id": "218588b8-fd5a-4a6f-82e1-d04bf11228e5"
}
],
"totalCount": 15,
"nextUrl": "https://our-api.azurewebsites.net/api/v1/Widgets?$top=1&$orderby=name&$skip=1"
}
The response includes the requested data, plus two other useful items: the total number of records that can be returned, and the URL to use to retrieve the next page of data.
BUT, there’s a BIG problem with that response. Here, let me show you…
https://our-api.azure-api.net/achme/api/v1/Widgets?$top=1
https://our-api.azurewebsites.net/api/v1/Widgets?$top=1&$orderby=name&$skip=1
See it? The hostnames and part of the paths are different! What’s going on??
Our ASP.NET Web API is actually hosted at https://our-api.azurewebsites.net/api/v1
. We don’t allow callers to hit our API directly though, we require that everyone goes through APIM, which lives at https://our-api.azure-api.net/achme/api/v1
.
When a request comes in to APIM, assuming there’s a valid subscription header accompanying the request, APIM happily proxies the request to our internal API, then hands the response back to the original caller.
The problem is that our API actually has no clue that it’s being proxied! When it builds up the nextUrl property, it thinks the request came directly to https://our-api.azurewebsites.net/api/v1
. And now we have a bad URL being handed back to the client!
We toyed with a few possible solutions. We could have added a config setting, plopped the APIM URL in there, and used that to build up the nextUrl. Instead, we opted to use APIM’s Policies to alter the response before its handed back to the caller.
APIM has numerous built-in policies, including one called "Finad and replace string in body." Sounds perfect, right? We applied this as an outbound policy to our API, which gave us the following:
<policies>
<!-- SNIP -->
<outbound>
<base />
<find-and-replace from="our-api.azurewebsites.net/api/v1" to="our-api.azure-api.net/achme/api/v1" />
</outbound>
</policies>
Policies do take a few seconds to take effect, but once it was in place, our API responses immediately changed:
{
"result": [
{
"name": "Widget 01",
"id": "218588b8-fd5a-4a6f-82e1-d04bf11228e5"
}
],
"totalCount": 15,
"nextUrl": "https://our-api.azure-api.net/achme/api/v1/Widgets?$top=1&$orderby=name&$skip=1"
}
And that’s exactly what we wanted! No code change, no new config settings to manage with our app, just a simple, one-line policy change, and everything is back to working as intended!