Solutions for consuming REST services from BizTalk has been around for a while, and Jon Flanders has an excellent post about it. However, very little has been told about exposing REST endpoints, and even less using JSON. If you don’t know about JSON, it’s a lightweight data format, commonly used by JavaScript and JQuery. Part from being less verbose then XML, it can be parsed to a object on the client which makes it easier to navigate (as oppose to using XPath). This can come to good rescue for UI devs who apparently don’t understand XPath 😉
I haven’t yet been in a situation where I’ve had to expose REST/JSON endpoints from BizTalk, but as Kent Weare was being hackled by Bil Simser (MS Word MVP), I was eager to help out.
I begun by creating a custom WCF MessageInspector. My plan was to parse the incoming JSON message to an XML message, and also to change the HTTP verb from GET to POST if the client sent a GET request (BizTalk requires POST). As it turns out, the HTTP verb/Method, can not be changed in the IDispatchMessageInspector. If it was to be changed it would have to be earlier in the channel stack.
Prior to the MessageInspector is the OperationSelector, so I went on creating one implementing the IDispatchOperationSelector interface. After moving the logic from the inspector to the SelectOperation method in the OperationSelector, I ran into a new problem. The method was never called. It seems BizTalk is adding it’s own OperationSelector through its HostFactory. As I wanted to host the Receive Location in a In-Process host (no IIS), making my own HosFactorythis wouldn’t work either
I was forced to dig even deeper in the WCF channel stack. Next step was a custom Encoder. Luckily I found a sample in the SDK which was pretty easy to use. The only problem was I couldn’t access the HTTP verb. However after all this, I was willing to accept this trade-off.
Next up was the serialization and deserialization of JSON. Bil Simser pointed me to the JSON.Net project on codeplex, which made it very easy:
XmlDocument doc = new XmlDocument(); doc.LoadXml(xmlString); string jsonString = JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.None, true);
How to use the sample:
- Download the sample here
- Either run the bLogical.JsonXmlMessageEncoder.Setup.msi or build and add the bLogical.JsonXmlMessageEncoder to the global assembly cache (GAC).
- Open BizTalk Administration Console. Browse to Adapters and right-click the WCF-Custom Receive Handler. Select Properties.
- Click the Import button, and select the WcfExtensions.config file found in the project.
- Deploy the FortisAlberta project to BizTalk.
- Import the FortisAlberta.BindingInfo.xml to the FortisAlberta Application
- Start the FortisAlberta Application.
- Run the WebApplication1 project, and submit an OutageReport.
How to configure a Receive Location manually
- Add a Request/Response Receive Port and Location.
- Set the transport to WCF-Custom (no need to host it in IIS).
- Set the binding to customBinding, and remove the existing binding elements.
- Add the "jsonXmlMessageEncoder" and the "http transport" extensions.
- Enable the port.
- You can use the XmlToJSONConverter that comes with the project to generate the expected JSON format from an XML instance, or use any of the online conversion sites like this one.
How to call the service
<script type="text/javascript"> jQuery.support.cors = true; var jsonRequest = '{"Tweet":{"Author":"wmmihaa","Text":"BizTalk Rock!"}}'; function ReportOutage() { $.ajax({ type: 'POST', url: http://yourdomain.com/submitTweet, data: jsonRequest, contentType: "application/json; charset=utf-8", dataType: "json", success: function (msg) { alert(msg); }, error: function (xhr, ajaxOptions, thrownError) { alert('error: ' + thrownError); } }); } </script>
Please not the JSON above:
“{"Tweet":{"Author":"wmmihaa","Text":"XML Rocks!"}}”. This is going to be translated to:
<Tweet><Author>wmmihaa</Author><Text>XML Rocks!</Text></Tweet>
As there are no namespace, you’d need to add one in the receive pipeline. Alternatively, you could add the namespace in JSON:
{"ns0:Tweet":{"@xmlns:ns0":"http://yourns.Tweet","Author":"wmmihaa","Text":"XML Rocks!"}}
Which would come out as:
<ns0:Tweet xmlns:ns0="http://yourns.Tweet"> <Author>wmmihaa</Author> <Text>XML Rocks!</Text> </ns0:Tweet>
How to call the service without using parameters
function ReportOutage() { $.ajax({ type: 'POST', url: "http://yourdomain.com/submitTweet", data: '{}', // empty parameter contentType: "application/json; charset=utf-8", dataType: "json", success: function (msg) { alert(msg); }, error: function (xhr, ajaxOptions, thrownError) { alert('error: ' + thrownError); } });
Empty parameters are casted to a message that looks like this: <EmptyJsonMessage/>. As you won’t have an equivalent schema in BizTalk, you can’t parse it using an XmlReceive pipeline. If you want to process the message in an orchestration, you’d need to set the message type of the incoming message to System.Xml.XmlDocument.
Restrictions
- Does not support HTTP GET.
- Does not support Uri parameters, Eg. http://server/Customers?id=16.
- The encoder supports both XML and JSON, but not both. It will be restricted to the media type set on the encoder.
- In this sample the response can not be handled in a streaming manner. If the size of the response message is bigger then what is read from the client, this is likely to cause a problem. I haven’t experienced this myself, but if you get into this problem, contact me and I’ll look into it.
HTH
Blog Post by: wmmihaa