A few months back, I wrote up a pair of blog posts (part 1, part 2) about the new BizTalk Server 2013 REST adapter. Overall, I liked it, but I complained about the apparent lack of support for using ampersands (&) when calling REST services. That seemed like a pretty big whiff as you find many REST services that use ampersands to add filter parameters and such to GET requests. Thankfully, my readers set me straight. Thanks Henry Houdmont and Sam Vanhoutte! You CAN use ampersands in this adapter, and it’s pretty simple once you know the trick. In this post, I’ll first show you how to consume a REST service that has an ampersand in the URL, and, I’ll show you a big gotcha when consuming ASP.NET Web API services from BizTalk Server.
First off, to demonstrate this I created a new ASP.NET MVC 4 project to hold my Web API service. This service takes in new invoices (and assigns them an invoice number) and returns invoices (based on query parameters). The “model” associated with the service is pretty basic.
public class Invoice { public string InvoiceNumber { get; set; } public DateTime IssueDate { get; set; } public float PreviousBalance { get; set; } public float CurrentBalance { get; set; } }
The controller is the only other thing to add in order to get a working service. My controller is pretty basic as well. Just for fun, I used a non-standard name for my query operation (instead of the standard pattern of Get<model type>) and decorated the method with an attribute that tells the Web API engine to call this operation on GET requests. The POST operation uses the expected naming pattern and therefore doesn’t require any special attributes.
public class InvoicesController : ApiController { [System.Web.Http.HttpGet] public IEnumerable<Invoice> Lookup(string id, string startrange, string endrange) { //yank out date values; should probably check for not null! DateTime start = DateTime.Parse(startrange); DateTime end = DateTime.Parse(endrange); List<Invoice> invoices = new List<Invoice>(); //create invoices invoices.Add(new Invoice() { InvoiceNumber = "A100", IssueDate = DateTime.Parse("2012-12-01"), PreviousBalance = 1000f, CurrentBalance = 1200f }); invoices.Add(new Invoice() { InvoiceNumber = "A200", IssueDate = DateTime.Parse("2013-01-01"), PreviousBalance = 1200f, CurrentBalance = 1600f }); invoices.Add(new Invoice() { InvoiceNumber = "A300", IssueDate = DateTime.Parse("2013-02-01"), PreviousBalance = 1600f, CurrentBalance = 1100f }); //get invoices within the specified date range var matchinginvoices = from i in invoices where i.IssueDate >= start && i.IssueDate <= end select i; //return any matching invoices return matchinginvoices; } public Invoice PostInvoice(Invoice newInvoice) { newInvoice.InvoiceNumber = System.Guid.NewGuid().ToString(); return newInvoice; } }
That’s it! Notice that I expect the date range to appear as query string parameters, and those will automatically map to the two input parameters in the method signature. I tested this service using Fiddler and could make JSON or XML come back based on which Content-Type HTTP header I sent in.
Next, I created a BizTalk Server 2013 project in Visual Studio 2012 and defined a schema that represents the “invoice request” message sent from a source system. It has fields for the account ID, start date, and end date. All those fields were promoted into a property schema so that I could use their values later in the REST adapter.
Then I built an orchestration to send the request to the REST adapter. You don’t NEED To use an orchestration, but I wanted to show how the “operation name” on an orchestration port is used within the adapter. Note below that the message is sent to the REST adapter via the “SendQuery” orchestration port operation.
In the BizTalk Administration console, I configured the necessary send and receive ports. The send port that calls the ASP.NET Web API service uses the WCF-WebHttp adapter and a custom pipeline that strips out the message of the GET request (example here; note that this will likely be corrected in the final release of BizTalk 2013).
In the adapter configuration, notice a few things. See that the “HTTP Method and URL Mapping” section has an entry that maps the orchestration port operation name to a URI. Also, you can see that I use an escaped ampersand (&) in place of an actual ampersand. The latter throws and error, while the former works fine. I mapped the values from the message itself (via the use of a property schema) to the various URL variables.
When I started everything up and send a “invoice query” message into BizTalk, I quickly got back an XML document containing all the invoices for that account that were timestamped within the chosen date range.
Wonderful. So where’s the big “gotcha” that I promised? When you send a message to an ASP.NET Web API endpoint, the endpoint seems to expect a UTF-8 unless otherwise designated. However, if you use the default XMLTransmit pipeline component on the outbound message, BizTalk applies a UTF-16 encoding. What happens?
Ack! The “newInvoice” parameter is null. This took me a while to debug, probably because I’m occasionally incompetent and there were also no errors in the Event Log or elsewhere. Once I figured out that this was an encoding problem, the fix was easy!
The REST adapter is pretty configurable, including the ability to add outbound headers to the HTTP message. This is the HTTP header I added that still caused the error above.
I changed this value to also specify which encoding I was sending (charset=utf16).
After saving this updated adapter configuration and sending in another “new invoice” message, I got back an invoice with a new (GUID) invoice number.
I really enjoy using the ASP.NET Web API, but make sure you’re sending what the REST service expects!
Great Article!
A minor correction. The more standard way to do content negotiation with web API or any HTTP service is to send the Accept header. Sending Content-Type header worked in your case because we fall back to Content-Type header if we don’t find a Accept header in web API. It is kind of weird though to have a Content-Type header without any content (GET requests).
And regarding the parameter to Post being null, we log deserialization errors in the ModelState property on the controller. Checkout this blog post (specially the ‘Applying Error Handling to Handle Invalid ModelStates’ section) for using an action filter to handle model state errors. http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx
Great points, thanks for adding!
I cannot for the life of me get the wcf-webhttp adapter to send anything other than POST actions. I have tried modifying the HTTP method and URL mapping to be identical to that shown in the images above, but it always reverts back to sending a POST request! I am not setting the WCF.Action or BTS.Operation anywhere. Specifically i am trying to send a GET request to a an ASP.NET MVC controller. Any ideas?
This is why I hate BizTalk. I deleted the send point and made a new one *exactly* the same and then it worked. There must be some kind of send port caching or something that isn’t reset with a hosts refresh.