The set body policy is used for changing the request or response body. If the policy is in the inbound policy section it changes the request payload passed to the back-end API, however, if it is in the outbound section it changes the response payload returned by API Management to the client.
Building a new body
In its simplest form the syntax is fairly straight forward. The following policy would replace the existing body with the string Hello World:
Hello World
There are no limits on how many times you can use the policy, however, a later instance of the policy will override the content of the previous policy. The body content is not appended or streamed in any way. The inner text of the set-body is buffered in a memory stream and delivered to the recipient.
Setting the body with static text is not particularly useful which is why we allow the use of expressions to dynamically determine the content. This next example takes the existing payload and updates all instances of the string sad with happy:
@(context.Body.As ().Replace("sad","happy"))
Any standard single line @(…), or multi-line @{…}expression can be used here. The expression is expected to return a UTF-8 encoded string. If the current Content-Type header has a charset parameter then it will be replaced with a utf8 value.
If the content being placed in the body is a completely different media type than the one received then the Content-Type header should be updated with a set-header policy.
When the set-body policy has been successfully applied, an entry is written out into the trace log.
Where it can be used
As well as using the in the inbound and outbound sections, it can also be used in the on-error and backend policy sections. It can be embedded inside a number of other policies such as return-response, choose, if, send-request, send-one-way-request.
Creating Complex Bodies
The ability to use arbitrary C# code to generate a new body does provide plenty of flexibility, however for some scenarios it can lead to some fairly intricate string concatenation code that is not the easiest to maintain. This is especially the case when goal is to extract some information from the input request body and simply reformat it before passing it along.
An alternative approach is to use a different mode of set-body which uses a Liquid Template to generate the new payload.
Liquid Template
In order to use a Liquid Template in the set-body policy a template attribute should be added to the element.
QueryString: {{context.Request.OriginalUrl.QueryString}}
From the above example, you can see it is possible to use an object model similar to the context object that is used in C# expressions in Liquid expressions. Unfortunately, the syntax is not identical. The Liquid binding syntax is limited to accessing values of maps and arrays. Therefore accessing header values would look like:
Calling User Agent: {{context.Request.Headers.User-Agent}}
Maps can be accessed either using simple dot notation or using square brackets and a string key:
Calling User Agent: {{context.Request.Headers["User-Agent"]}}
The headers object is actually a map of string array, so the result of the above bindings is a concatenation of all the header values together. To access a single, header value, the array index operator can be used.
First Via: {{context.Request.Headers.Via[0]}}
Manipulating the Existing Body
Liquid templates can be convenient for changing the format of a payload. Consider this policy:
orderid: {{body.order.id}} date: {{body.order.orderdate}} customer: id: {{body.order.customer.id}} name: {{body.order.customer.name}}
Using the body binding object you are able to access fragments of either an XML or JSON based payload. XML is binding is currently limited to accessing element values. More specifically, you can only access the text node within an element that only contains a text node. You cannot access text nodes of elements that contain mixed content or XML attribute values. You can use the dot operator to navigate objects and square brackets to index into arrays. It is essential that you send a Content-Type header in the request to indicate to the binder how to interpret the body. You can use application/json or any media type that ends with +json for processing as JSON and application/xml or any media type that ends in +xml to process the body as XML.
The above template could equally bind to either of the following payloads:
2323 2017-10-01 4545 Copper Clogs { "order": { "id":"2323", "date": "2017-10-01", "customer": { "id": "4545", "name": "Copper Clogs" } } }
Transforming Values
Beyond being able to use binding to extract values from payloads, Liquid has the ability to transform individual values using what it calls filters. One common use of filters to control how dates are rendered.
date: {{body.order.orderdate | Date:"yyyy-MM-dd"}}
The original Liquid project was a Ruby based project and there are certain Rubyisms that exist in the templating language. However the library we use is dotLiquid which is a .net port, which allows for some variations on the original syntax. The date format in the example above is a .net style date format rather than a Liquid native Ruby style date format string. Another .Net style issue is the use of pascal-cased filter instead of camel-cased. This trips up people quite regularly as they look for Liquid documentation and find examples that use date for the filter and that doesn't work.
Another tricky aspect of the current implementation of filters is the need to escape certain characters. Parameters can be quoted using either single or double quotes, but special characters will need to be escaped using the character.
date: {{body.path | Replace: '/','' }}
Collections of Things
For dealing with collections of items in a payload there is a for loop tag will work for most purposes. The following example creates a list of order item descriptions in YAML.
orderid: {{body.order.id}} date: {{body.order.orderdate}} customer: id: {{body.order.customer.id}} name: {{body.order.customer.name}} items: {%for item in body.order.items %} - {{ item.description }}{% endfor %}
However, there are a couple of additional challenges when writing JSON and reading from XML based payloads. In JSON it is necessary to delimit items in a list with a comma. Unfortunately there is no simple way in Liquid to prevent the trailing comma after the last item in the list. To address this we created a custom looping mechanism.
{ "items": [ {%JSONArrayFor item in body.order.items %}"{{ item.description }}"{% endJSONArrayFor %} ] }
A second challenge faced when dealing with converting XML to JSON when the XML contains heterogenous collections. Consider a payload like this where you only want to extract the TaxItem elements:
The JSONArrayFor has a where clause that can filter XML elements that will be looped over. P1 P2 S1 T1 T2 T3 { "items": [ {% JSONArrayFor item in body.order.items where TaxItem %} "{{ item }}" {% endJSONArrayFor %} ] }
Cost
Using set-body does require buffering the newly generated body, and consuming all of the old body. This means for HTTP interactions that depend on streaming of content, using chunk encoding, set-body may introduce an unacceptable cost. As always, test your policies under load before putting them into production.
Summary
A major role of any API Management gateway is to provide a façade over back-end APIs. Being able to transform request and response bodies are key to achieving this. The variety of capabilities of the set-body policy provide a range of tools to manipulate the HTTP payload in the most effective way.