One of the core tenets of RESTFul architecture is that APIs should support content negotiation. That is to say that the service consumer should be able to tell the service through the use of the Accept HTTP header what format the response message should be returned in. On the flip side, it is commonly accepted that RESTFul APIs should support a range of request formats; typically XML and JSON at the very least, and that the Content-Type HTTP header is used to convey the format.
BizTalk Server 2013 R2 delivered the new JSON Decoder and Encoder pipeline components that at last treat JSON as a first class format in BizTalk Server. Internally, BizTalk maps will still only work with XML as will a range of other BizTalk functions, however at the very least JSON is now accepted on the outskirts. It is possible to get JSON support in older versions of BizTalk however you will have to create custom pipeline components to cater for this, since Microsoft has only added out of the box functionality in BizTalk Server 2013 R2.
Unfortunately, the traditional means of employing these pipeline components comes with some rigidity. If you want to expose a RESTFul API using a WCF-WebHttp receive location then you will need to choose whether or not to include a JSON Decoder pipeline component in your receive pipeline. On the flip side, you will need to choose whether to use a JSON Encoder on your send pipeline. There is no way to dynamically decide whether to employ the JSON Decoder and Encoder based on the Content-Type and Accept header on the request message without employing an orchestration. And it gets even worse…what if you wanted to build an API that also supported an HTML response type?
I’m not against the use of orchestration when they are used to provide some form of workflow or process orchestration, however I do not like being forced to use orchestration due to technical constraints.
I have addressed this problem for the most part using the BRE Pipeline Framework. Because JSON decoding and encoding is only supported by BizTalk Server 2013 R2, I have addressed this problem in a custom MetaInstruction, or an extension to the framework, rather than making a change to the core framework. Note that this extension requires that you have the BRE Pipeline Framework v1.6.0 or above installed on your machine. This new functionality is catered for via the new BREPipelineFramework.JSONInstructions vocabulary.
To get started download the installer for the extension and install it on your BizTalk 2013 R2 machine. Install the vocabulary using the Business Rules Engine Deployment Wizard, the vocabulary file being found in the program files folder which by default is “C:\Program Files (x86)\BREPipelineFramework.JSONInstructions”.
Here’s a quick rundown on what these vocabulary definitions do.
- DecodeJSON – This vocabulary definition executes the JSON Decoder against the message. You will need to specify a root node name and namespace with which the decoded XML will be wrapped in.
- EncodeJSON – This vocabulary definition executes the JSON Encoder against the message. You will need to specify whether the root node will be stripped off prior to encoding or not.
- ExecuteXSLTTransform – This vocabulary definition will execute an XSLT transformation based on an XSLT file which you must provide a file location for. This is handy if you want to transform to/from HTML.
- AssessContentType – This vocabulary definition lets you assess what the content type of your request message is so you can decide how to process it. It will first attempt to derive the content type by studying the Content-Type HTTP header on the request. If no Content-Type header is available then it will probe the first character of the message to determine if it is XML or JSON.
- CacheAcceptHeader – Since we need to assess the Accept header value when choosing how to encode the response message, we must cache the Accept header value while processing the request. If we did not do this then we would not have access to the value later.
- AssessCachedAcceptHeader – This vocabulary definition lets you assess what the desired content type is based on the cached Accept header. This can be executed on the send pipeline that is used to encode the response message.
- GetCachedAcceptHeader – This vocabulary definition will return the raw value of the cached accept header. This can be executed on the send pipeline that is used to encode the response message. Only use this if you aren’t happy with the logic used by the AssessCachedAcceptHeader vocabulary definition.
The following screenshot demonstrates how we can dynamically decode incoming request messages based on the derived content type of the request message. Note that if the message was XML instead of JSON then this rule would not first. Note as well that we are caching the Accept header value for later use…theoretically this should be done in a separate rule which will execute regardless of the request content type but I have collapsed the two actions into one rule for brevity.
The following screenshot demonstrates the assessment of the cached Accept header value to dynamically encode the response message to JSON.
And the next screenshot similarly dynamically transforms the response to HTML by executing custom XSLT.
So how do you make use of this new vocabulary? The easiest way to do this is to put the fully qualified class/assembly name of the custom MetaInstruction in the CustomMetaInstructionSeparatedList parameter (which takes in a ; delimited list) of the BREPipelineFrameworkComponent pipeline component as below. Once you’ve done this then the vocabulary will be available for use in the ExecutionPolicy.
The fully qualified class/assembly name value for the JSON extension is as below.
So what are the shortcomings of the current solution?
- The response content type derivation based on the cached Accept header doesn’t currently go as far as is normally expected by RESTFul standards. These standards dictate that multiple content types can be specified and each can be given priority weightings. The solution I’ve put in place is just a v1, and it will always prioritize as follows regardless of explicit priorities – JSON > XML > URLEncodedForm > CSV > HTML > Text > Other. If you don’t like this logic then you can always make your own choice by assessing the raw Accept header value with the GetCachedAcceptHeader vocabulary definition instead of the AssessCachedAcceptHeader vocabulary definition.
- I have not yet found a context driven way of overriding the Content-Type HTTP header on the response message. This will always be set to Application/XML, unless you have set an alternate value on the Messages tab of your receive location’s configuration in which case it will always be that alternative value. For now the only way I can think of overriding this is via a WCF behavior.
- It only works with BizTalk Server 2013 R2, but you could easily get it to work with BizTalk 2013 by creating your own custom MetaInstruction and vocabulary, and for the most part you can just reuse my own code except in your MetaInstruction you reference your custom JSON encoding/decoding pipeline components. You can find the source code for the extension at “$/BREPipelineFramework/Main/BREPipelineFramework.BizTalk2013.R2/BREPipelineFramework.JSON” in the BRE Pipeline Framework source code repository.