Exam Objectives
Consume Web API services by using HttpClient synchronously and asynchronously; send and receive requests in different formats (JSON/HTML/etc.); request batchingQuick Overview of Training Materials
Exam Ref 70-487 - Chapter 4.5
[MSDN] Calling a Web API From a .NET Client (C#)
[Code-Maze] A few great ways to consume RESTful API in C#
[MSDN] Calling a Web API From a .NET Client (C#)
[Code-Maze] A few great ways to consume RESTful API in C#
[Blog] ASP.NET Web API Request Batching
[Blog] You're using HttpClient wrong and it is destabilizing your software
[StackOverflow] How do you set the Content-Type header for an HttpClient request?
[MSDN] Introducing batch support in Web API and Web API OData
[Blog] You're using HttpClient wrong and it is destabilizing your software
[StackOverflow] How do you set the Content-Type header for an HttpClient request?
[MSDN] Introducing batch support in Web API and Web API OData
Consume services with HttpClient
I'm going to go out on a limb and assume that the version of HttpClient we care about here is the one from the System.Net.Http namespace, NOT the one from the Windows.Web.Http namespace (which appears to be intended for UWP and only works on .NET 4.5+). The demos in "Consuming the API" section of the "Building and Securing a RESTful API for Multiple Clients in ASP.NET" PluralSight course use this client, so I'm gonna roll with that. The Exam Ref also assumes this class (though he doesn't come out and say it). There are extension methods for the HttpClient class used by the Exam Ref, which are part of the HttpClientExtensions class.
The Calling a Web API From a .NET Client (C#) documentation walks through how to use an HttpClient instance to interact with a RESTful API. I didn't feel like reproducing the server side api just for the sake of implementing an HttpClient demo, so I'm using the simple open api provided at https://dog.ceo/dog-api/. This is a read-only API, so I can just do GET requests, but I think it will get the point across.
The DogApiClient class implements one API method: get all breeds. The part played by HttpClient is pretty minimal; most of the interesting code is dealing with the deserialization with the Newtonsoft.Json libraries:
The simple console app just calls this method and prints out all the dog breeds:
Major breeds get a line for each subbreed because of the linq expression SelectMany (credit to StackOverflow for that one...), and the anonymous object deserialization approach was in the Newtonsoft documentation.
The CodeMaze article gives a little bit of background on alternative approaches to using HttpClient to make web requests (as well as HttpClient itself, though the author links to a Channel9 presentation on the UWP version, probably a bit of confusion...). HttpWebRequest is a lower level abstraction for making requests that gives you much finer control over how the request is executed. This reminded me of clients I'd written in Java using URLConnection... anyway...
While it wasn't necessary for me to fiddle with the client too much, one common need when dealing with such a client is adding additional request headers. The client object has a property called DefaultRequestHeaders, with an Add method that takes two strings, effectively adding a header to the collection. The HttpClient class has methods for sending GET, POST, PUT, and DELETE, it also includes a SendAsync() method which takes an HttpRequestMessage object. This object can be used basically like a configuration object, allowing you to set headers or alternate methods (PATCH) for a single request (which would be a better approach to take when using a single static HttpClient object).
One final thing worthy of note regarding the HttpClient class is that using it incorrectly can lead to resource leaks. This is detailed in the ASP.NET Monsters article. The pattern he'd been using that caused him trouble was to instantiate a new HttpClient in a using block. Even though this was disposing of the instance, it was still sucking up all the available sockets. The simple solution was to reuse a single static instance of the HttpClient class for all requests. The article is interesting and worth a look, especially the comments which point out some of the weaknesses of this approach (caching, load balancing) and potential alternative approaches.
The Calling a Web API From a .NET Client (C#) documentation walks through how to use an HttpClient instance to interact with a RESTful API. I didn't feel like reproducing the server side api just for the sake of implementing an HttpClient demo, so I'm using the simple open api provided at https://dog.ceo/dog-api/. This is a read-only API, so I can just do GET requests, but I think it will get the point across.
The DogApiClient class implements one API method: get all breeds. The part played by HttpClient is pretty minimal; most of the interesting code is dealing with the deserialization with the Newtonsoft.Json libraries:
public class DogApiClient { private static HttpClient client = new HttpClient(); private const string baseUrl = "https://dog.ceo/api/"; public async Task<IEnumerable<string>> GetBreedsListAsync() { IEnumerable<string> list = null; string path = baseUrl + "breeds/list/all"; HttpResponseMessage response = await client.GetAsync(path); var definition = new { status = "", message = new Dictionary<string,List<string>>() }; if (response.IsSuccessStatusCode) { string rawJson = await response.Content.ReadAsStringAsync(); list = JsonConvert.DeserializeAnonymousType(rawJson, definition) .message .SelectMany(b => b.Value.DefaultIfEmpty() .Select(s => b.Key + " " + s)); } return list; } //same method without all the async-iness... public IEnumerable<string> GetBreedsList() { IEnumerable<string> list = null; string path = baseUrl + "breeds/list/all"; HttpResponseMessage response = client.GetAsync(path).Result; var definition = new { status = "", message = new Dictionary<string, List<string>>() }; if (response.IsSuccessStatusCode) { string rawJson = response.Content.ReadAsStringAsync().Result; list = JsonConvert.DeserializeAnonymousType(rawJson, definition) .message .SelectMany(b => b.Value.DefaultIfEmpty() .Select(s => b.Key + " " + s)); } return list; } //if we wanted to be super lazy we could do it this way... public IEnumerable<string> GetBreedsList2() { return GetBreedsListAsync().Result; } }
The simple console app just calls this method and prints out all the dog breeds:
Major breeds get a line for each subbreed because of the linq expression SelectMany (credit to StackOverflow for that one...), and the anonymous object deserialization approach was in the Newtonsoft documentation.
The CodeMaze article gives a little bit of background on alternative approaches to using HttpClient to make web requests (as well as HttpClient itself, though the author links to a Channel9 presentation on the UWP version, probably a bit of confusion...). HttpWebRequest is a lower level abstraction for making requests that gives you much finer control over how the request is executed. This reminded me of clients I'd written in Java using URLConnection... anyway...
While it wasn't necessary for me to fiddle with the client too much, one common need when dealing with such a client is adding additional request headers. The client object has a property called DefaultRequestHeaders, with an Add method that takes two strings, effectively adding a header to the collection. The HttpClient class has methods for sending GET, POST, PUT, and DELETE, it also includes a SendAsync() method which takes an HttpRequestMessage object. This object can be used basically like a configuration object, allowing you to set headers or alternate methods (PATCH) for a single request (which would be a better approach to take when using a single static HttpClient object).
One final thing worthy of note regarding the HttpClient class is that using it incorrectly can lead to resource leaks. This is detailed in the ASP.NET Monsters article. The pattern he'd been using that caused him trouble was to instantiate a new HttpClient in a using block. Even though this was disposing of the instance, it was still sucking up all the available sockets. The simple solution was to reuse a single static instance of the HttpClient class for all requests. The article is interesting and worth a look, especially the comments which point out some of the weaknesses of this approach (caching, load balancing) and potential alternative approaches.
Send and Receive in different formats
Yeeeeah I think the Design a Web API (Choose appropriate format) and Implement a Web API (Accept JSON data, Content Negotiation) posts cover this for the most part. The important thing to remember is that you need to use the Accept header from the client to specify which media formats you want to accept, and use the Content-Type header to tell the server what kind of data you are sending when posting data back to the server. If the data you want to deal with is not included by default, you'll need to implement a custom MediaFormatter (see the Design post for an example).
Request Batching
The blog post ASP.NET Web API Request Batching demonstrates how to do request batching in Web API (though on second inspection it seems it's probably mostly lifted from the documentation, which actually has a more varied example...). Using the batching functionality requires a simple change on the server, and special treatment on the client. In the server, it is only necessary to map a special batching route:
One thing to watch out for that tripped me up: I initially defined this route after the super generic default route, and as a result all my requests to this were being intercepted and forwarded to a nonexistent "batch" controller, resulting in a 404 response. So don't do that...
Most of the complication with this approach falls to the client. The examples follow this basic template:
//Add batch route config.Routes.MapHttpBatchRoute( routeName: "batch", routeTemplate: "api/batch", batchHandler: new CustomHttpBatchHandler(GlobalConfiguration.DefaultServer) );
One thing to watch out for that tripped me up: I initially defined this route after the super generic default route, and as a result all my requests to this were being intercepted and forwarded to a nonexistent "batch" controller, resulting in a 404 response. So don't do that...
Most of the complication with this approach falls to the client. The examples follow this basic template:
- requests are defined as HttpRequestMessage objects, complete with method, url, headers, etc.
- these requests are passed into a new HttpMessageContent object
- a MultipartContent object is created, and all the HttpMessageContent objects added to it
- the MultipartContent object is set as the content to a new HttpRequestMessage, with a method of POST and the url set to the batch endpoint
- HttpClient is used to send this message
- a MultipartMemoryStreamProvider is used to pull out the individual responses from the Content property on the response
static void Main(string[] args) { HttpClient client = new HttpClient(); //create the requests to be batched string baseUrl = "http://localhost:34245/api/Dogs/"; var getDog1 = new HttpRequestMessage(HttpMethod.Get, baseUrl + "1"); var getDog3 = new HttpRequestMessage(HttpMethod.Get, baseUrl + "3"); var getGizmo = new HttpRequestMessage(HttpMethod.Get, baseUrl + "Gizmo"); getGizmo.Headers.Add("Accept", @"application/Dog"); //create the batch container MultipartContent content = new MultipartContent("mixed", "batch_" + Guid.NewGuid().ToString()); content.Add(new HttpMessageContent(getDog1)); content.Add(new HttpMessageContent(getDog3)); content.Add(new HttpMessageContent(getGizmo)); //create the batch request HttpRequestMessage batchRequest = new HttpRequestMessage(HttpMethod.Post, "http://localhost:34245/api/batch"); batchRequest.Content = content; //send the batch request with the client and parse the response HttpResponseMessage response = client.SendAsync(batchRequest).Result; MultipartMemoryStreamProvider responseContents = response.Content.ReadAsMultipartAsync().Result; foreach(var c in responseContents.Contents) { Console.WriteLine(c.ReadAsStringAsync().Result + "\n\n"); } Console.ReadLine(); }
Playing around with Fiddler we can see what the batch request is actually doing:
Each part of the multipart message contains the information necessary for Web API to route that request the way it otherwise would have. This stack overflow question also deals with batching, though I didn't experience his particular problem (I just needed to do the routing in the right order).
No comments:
Post a Comment