Yes Richard, You Can Use Ampersands in the BizTalk REST Adapter (And Some ASP.NET Web API Tips)

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.

2013.03.19.rest01

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.

2013.03.19.rest03

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.

2013.03.19.rest02

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).

2013.03.19.rest04

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 (&amp;) 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.

2013.03.19.rest05

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.

2013.03.19.rest06

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?

2013.03.19.rest07

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.

2013.03.19.rest08

I changed this value to also specify which encoding I was sending (charset=utf16).

2013.03.19.rest09

After saving this updated adapter configuration and sending in another “new invoice” message, I got back an invoice with a new (GUID) invoice number.

2013.03.19.rest10

I really enjoy using the ASP.NET Web API, but make sure you’re sending what the REST service expects!

Author: Richard Seroter

Richard Seroter is Director of Developer Relations and Outbound Product Management at Google Cloud. He’s also an instructor at Pluralsight, a frequent public speaker, the author of multiple books on software design and development, and a former InfoQ.com editor plus former 12-time Microsoft MVP for cloud. As Director of Developer Relations and Outbound Product Management, Richard leads an organization of Google Cloud developer advocates, engineers, platform builders, and outbound product managers that help customers find success in their cloud journey. Richard maintains a regularly updated blog on topics of architecture and solution design and can be found on Twitter as @rseroter.

8 thoughts

  1. 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

  2. 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?

    1. 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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.