Improving HttpClient Requests
[C#, HttpClient]
This is another improvement to the logic of making a Http
request using the HttpClient.
As has been discussed before, a number of problems manifest, the main one being that in .NET Core (and .NET 5) redirects between http
and https
are not natively honoured by the HttpClient
.
In a previous post where we were getting the details of currently playing music on WQXR, we had written the following code to check the response for a redirect and then made a second request to the Uri
specified in the header
var result = "";
// Make the initial request
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
// Check if the status code of the response is in the 3xx range
if ((int)response.StatusCode >= 300 && (int)response.StatusCode <= 399)
{
// It is a redirect. Extract the location from the header
var finalResponse = await client.GetAsync(response.Headers.Location);
if (finalResponse.IsSuccessStatusCode)
{
// Now make the request for the json
result = await finalResponse.Content.ReadAsStringAsync();
}
else
{
Console.WriteLine($"Could not get the result, error: {finalResponse.StatusCode}");
}
}
else
{
// This isn't a redirect. Read the response
result = await response.Content.ReadAsStringAsync();
}
The problem with doing it this way is that we are assuming that there is only one redirect.
This is probably often the case, but it is not a good assumption to make. There may be more than one redirect, for any number of reasons.
So we can make couple of improvements to make this better.
The first is that we can encapsulate the logic for checking for http redirects.
We start with this line.
if ((int)response.StatusCode >= 300 && (int)response.StatusCode <= 399)
Using this logic we can create a static class that we can use to contain an extension method for the HttpResponseMessage class.
public static class HttpResponseExtensions
{
public static bool IsRedirect(this HttpResponseMessage message)
{
return (int)message.StatusCode >= 300 && (int)message.StatusCode <= 399;
}
}
We can then create another extension method for the HttpClient class, in which we don’t assume there is a single redirect and we recursively call the method as many times as necessary.
public static class HttpClientExtensions
{
public static async Task<string> MakeRequest(this HttpClient client, string url)
{
var formattedResult = "";
Log.Debug("Requesting {url}", url);
// Make the initial request
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
// Check if the status code of the response is in the 3xx range
if (response.IsRedirect())
{
// This is a redirect. Make a recursive call
return await MakeRequest(client, response.Headers.Location.ToString());
}
else
{
// This isn't a redirect. Read the response
formattedResult = JToken.Parse(await response.Content.ReadAsStringAsync()).ToString();
}
return formattedResult;
}
}
Finally, our encapsulated logic can easily be called as follows:
using (var client = new HttpClient())
{
// Output the json
Console.Write(await client.MakeRequest("https://api.wnyc.org/api/v1/whats_on/wqxr-special2"));
}
Doing it this way means that we can easily reuse this logic.
A further improvement is to make the method return a HttpResponse object directly, rather than a string. This gives more flexibility in the management of the logic.
Running the code should yield the following:
Note the two logged requests at the very top.
The code is in my Github.
Happy hacking!