CSS Header for code highlighting

Friday, January 18, 2013

Calling HttpClient async functions from constructors in ASP.NET 4.5

Recently, we have been using async/await a lot in our ASP.NET MVC project. It is very powerful. However, as with most power, comes great responsibility. In multithreading, this adage can be translated to "with great parallelism, don't deadlock".
As it has been documented and explained, in the current ASP.NET stack, when you await during a request, you will get a callback on the originating thread (that's the point of await/async after all). When you make an awaitable call to let's say HttpClient.GetStringAsync(), it will want to come back on that thread also. As well explained by Stephen Cleary in his StackOverflow answer (here), this goes south pretty much immediately if you start blocking somewhere in the call stack. This is true if you call a Task<T>.Result.

No async in constructors

Unfortunately, for good reasons, constructors cannot be async. Therefore, they cannot await on anything. In our case, we needed to retrieve some data from another service within a constructor to initialize some configuration. We are making extensive use of MEF, so we pretty much let the container resolve what it needs for us. This was an issue as there is no way to make a synchronous call to HttpClient to get data.

Solution

Fortunately, instead of forcing the issue and make the all to HttpClient.GetStringAsync() blocking by using Task<T>.Result, we went the opposite direction and made the call twice asynchronous:
var task = Task.Factory.StartNew(()=>client.GetStringAsync("url").Result);
var data = task.Result;
If I get this right, the reason this works is because the first line will start a new task on a separate thread. It is then on that second thread that the GetStringAsync will callback, leaving the main thread available for the outgoing call. If you think I am way off and have a good explanation, please share. I am not an expert on the subject at this time.
In the mean time, I hope this will get a few people out of a tough spot.

Sunday, January 6, 2013

HttpClient, Basic authentication and Bing

On Friday, I was trying to use the Bing API to test an issue we were having while using .NET 4.5, LINQ and async/await patterns. While Bing recommends you use their .NET library, I needed to keep my example as a pure core libraries example so people could try to reproduce my issue without having to download any third party library.
The Bing API offers Basic authentication or OAuth (to keep track of how much your are using of your monthly quota). So, I had to make a remote call to a web service using Basic authentication (to keep it simple). In .NET 4.5, the recommended way tot access remote resources is to use HttpClient. I spent a fair bit of time researching this online and either it is so simple or nobody ever tried, but bottom line was that nobody has a clear and simple example
There are several key elements to a successful Bing query using HttpClient.

Header info

According to the RFC 2617, basic authentication information is passed as an HTTP header. Fortunately, the HttpClient instance does expose its header information and does provide access to common headers, including Authorization. The proper syntax is:
using (var client = new HttpClient(new HttpClientHandler(), true))
{
   client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", "<your authenciation value>");
    ...
}

Authentication value

Following the instructions from the Bing API FAQ, I grabbed my key and slapped it on as my authentication parameter. No luck, all I got was this message:
The authorization type you provided is not supported.  Only Basic and OAuth are supported
A bit more digging revealed that I had to encode the data properly. Not a problem, .NET provides an easy way to do this:
using (var client = new HttpClient(new HttpClientHandler(), true))
{
    var encodedKey = Convert.ToBase64String(Encoding.ASCII.GetBytes("my-Bing-API-Key"));
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", encodedKey);
    ...
}
Same error message. Using Fiddler, I confirmed that my data was being sent properly.

The Solution

Finally, after noticing a post on a related issue, I realized that the format of the encoded string is username:password. Since the Bing API migration document states to only specify the password when using a browser, all I had to do was this:
using (var client = new HttpClient(new HttpClientHandler(), true))
{
    var bingKey = "my-bing-api-key";
    var authentication = string.Format("{0}:{1}", string.Empty, bingKey);
    var encodedKey = Convert.ToBase64String(Encoding.ASCII.GetBytes(authentication));
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", encodedKey);
    var data = await client.GetStringAsync("https://api.datamarket.azure.com/Bing/Search/v1/Web?Query=%27otixo%27");
}
I hope this will help the next person trying to get something apparently so trivial.