Introduction
[Level T2] So far we have only covered server-side features while Microsoft's ASP.NET Web API has excellent client-side features. It is true that server-side requires a lot of care and attention but as I have pointed out in the past, implementing REST-style application usually involves a client burden. In this post, we will cover Russian Doll model on the client (for more background have a look at part 6).
Client arsenal in the age of Web API
Before Microsoft HTTP history was re-written in ASP.NET Web API, there were 3 ways to make an HTTP request:- High-level WebClient. While lacked some tuning features, it presented a useful tool capable of HTTP, FTP and file access.
- Mid-level HttpWebRequest: This was used most often and presented abstractions at the right level.
- Close-to-the-metal Windows socket. While you could, I cannot think of a reason why you would.
While these could still prove useful, Microsoft has designed and built HttpClient class and it is intended to be used as the main client API in the Web Stack. If you read Mike Wasson's excellent post on Message Handlers you will know why: it has been built using the same abstractions as the server side of Web API. This enables lego-esque pipeline of various message handlers (as we have seen before) each to add a nice little feature to the pipeline.
HttpClient
First of all HttpClient is heavily asynchronous - well that is an understatement, it is purely asynchronous.Using HttpClient is simple:
var client = new HttpClient(); var asyncTask = client.GetAsync("http://webapibloggers.com"); asyncTask.ContinueWith(task => { Thread.Sleep(2000); task.Result.Content.ReadAsStringAsync() .ContinueWith(readTask => Console.WriteLine(readTask.Result)); }); Console.WriteLine("Waiting ..."); Console.Read();
or even simpler:
string result = client.GetAsync("http://webapibloggers.com") .Result.Content.ReadAsStringAsync().Result;
or even simpler:
var result = client .GetStringAsync("http://webapibloggers.com").Result;
There is not much magic in using HttpClient. Basically you may use
- GetAsync: analogous to HTTP GET
- PostAsync: analogous to HTTP POST
- PutAsync: analogous to HTTP PUT
- DeleteAsync: analogous to HTTP DELETE
- GetStringAsync, GetByteArrayAsync and GetStreamAsync if all you care is the Content
- SendAsync: general method if you want to have full control on constructing an HttpRequestMessage or want to use other/custom verbs.
Specifying an HttpMessageHandler
When you create an HttpClient using its parameterless constructor, in fact this is what you are doing:public HttpClient() : this(new HttpClientHandler()) { }
So basically the default constructor creates an HttpClientHandler to handle the all leg work for passing the request over to the server and then construct an HttpResponseMessage based on the result.
HttpClientHandler in fact uses HttpWebRequest and HttpWebResponse behind the scene (discussion here) but translates the communication to ASP.NET-agnostic model of the Web API, a.k.a. HttpRequestMessage and HttpResponseMessage.
The the other two constructors we can pass an HttpMessageHandler. This message handler has to be the handler that ultimately returns the result - usually from the server over the wire. Well, I said usually since nothing stops us to put a full-fledged HttpServer in there: this means the request and response will not even touch the wire and will run fully in-memory. Pedro Felix shows us how to do this here.
HTTP pipeline on the client |
What if we do not want to mess around with all the whole communication pipeline and we just want to use a DelegatingHandler? Well, not so difficult, all we have to do is to create a delegating handler and set its InnerHandler to an HttpClientHandler.
Here we look our minimal scenario where we have delegating handler that outputs Uri into the Trace:
public class LoggingHandler : DelegatingHandler { public LoggingHandler() { InnerHandler = new HttpClientHandler(); } protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Trace.WriteLine(request.RequestUri.ToString()); return base.SendAsync(request, cancellationToken); } } class Program { static void Main(string[] args) { var client = new HttpClient(new LoggingHandler()); var result = client.GetStringAsync("http://webapibloggers.com").Result; Console.WriteLine(result); Console.Read(); } }
We can see the trace output in the Visual Studio output window.
Real-world client message handlers
Admittedly, there are far less implementation of message handlers on the client perhaps since most early adopters of ASP.NET Web API have been server-focused geeks. Yet, there are some implementations out there.Henrik explains how to build an OAuth message handler and use it to build a twitter client. This sample can be downloaded from here.
I am hoping to build a CachingHandler on the client using ASP.NET Web API. If all goes well, this should be the topic of the next post.
Conclusion
ASP.NET Web API offers the same wealth of HTTP pipeline abstractions (known as Russian Doll model) on the client as on the server. HttpClient is the main tool to make HTTP calls and it - by default - uses HttpClientHandler for all its communications. If you need to provide a delegating handler (or a series of delegating handlers) you can pass it to the constructor while setting the InnerHandler to an instance of HttpClientHandler.
Hi Byte,
ReplyDeleteI'm building a API using Web API for both sides provider and consumer. At the provider side I'm using Web API and at the API consumer site I'm using HttpClient. Because the provider require the consumer authenticate using Digest Authentication, so I need to write a custom DelegatingHandler to catch the request and inject Digest authentication details into the header. For each response from the provider, I check if I got a 401 Unauthorized then I need to get the header, extract Digest details supplied by the servers, generate an Authorization header and re-send the request to to the provider. Below is my custom DelegatingHandler:
public class DigestAuthDelegatingHandler : DelegatingHandler
{
public DigestAuthDelegatingHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode && response.StatusCode == HttpStatusCode.Unauthorized)//This line of code is never reached
{
//Generate the Digest Authorization header string and add to the request header,
//then try to resend the request to the API provider
}
return response;
}
}
I create a HttpClient and add my custom DelegatingHandler to the message handlers pineline
HttpClient httpClient = new HttpClient(new DigestAuthDelegatingHandler(new HttpClientHandler()));
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.BaseAddress = new Uri("http://127.0.0.1/");
HttpResponseMessage getTransactionInfoResponse = httpClient.GetAsync("api/getTransactionInfo?TransactionNumber=1000).Result;
After doing that, it look like that my consumer runs forever. When I add a break point AFTER the code line await base.SendAsync above, I see that the code will never return, so I have no way to check if the response is get an 401 unauthorized to extract the Digest authorization header details. Nothing wrong at the API provider because I've successfully built another API consumer site using the traditional WebHttpRequest support Digest Authenticate and it works well.
Is there anything wrong I'm doing ? If you can help me It would be very appreciated !
Thanks in advanced