System.Net.Http.Json
's HttpClient
extension methods such as GetFromJsonAsync()
greatly simplifies the routine codes to retrieve json objects from a web API. It's a pleasure to use.
But because of the way it's designed (returning deserialized objects directly), it does not produce any HttpResponseMessage
for inspection that allows me to take custom actions based on HttpStatusCode
.
Instead, non-success status codes results in a HttpRequestException
, which does not appear to offer any properties that expose strongly typed HttpStatusCode
. Instead, the status code is included in the exception's Message
string itself.
Edit: .NET 5.0 added the HttpRequestException.StatusCode
property, so it can now be inspected when calling GetFromJsonAsync
.
//old post below
So I've been doing something like this:
try
{
var cars = await httpClient.GetFromJsonAsync<List<Car>>("/api/cars");
//...
}
catch (HttpRequestException ex)
{
if (ex.Message.Contains(HttpStatusCode.Unauthorized.ToString()))
{
//Show unauthorized error page...
}
//...
}
This feels a bit hacky. With the old school way of creating HttpRequestMessage
and calling SendAsync
, we naturally got the chance to inspect a response's HttpResponseMessage.StatusCode
. Adding some of those codes back would defeat the convenient purpose of using the one-liners in System.Net.Http.Json
.
Any suggestions here would be greatly appreciated.
You can use:
// return HttpResponseMessage
var res= await httpClient.GetAsync<List<Car>>("/api/cars")
if (res.IsSuccessStatusCode)
var cars = res.Content.ReadFromJsonAsync<List<Car>>();
else
// deal with the HttpResponseMessage directly as you used to
I use a base class like this:
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
namespace MyProject.ClientAPI
{
public abstract class ClientAPI
{
protected readonly HttpClient Http;
private readonly string BaseRoute;
protected ClientAPI(string baseRoute, HttpClient http)
{
BaseRoute = baseRoute;
Http = http;
}
protected async Task<TReturn> GetAsync<TReturn>(string relativeUri)
{
HttpResponseMessage res = await Http.GetAsync($"{BaseRoute}/{relativeUri}");
if (res.IsSuccessStatusCode)
{
return await res.Content.ReadFromJsonAsync<TReturn>();
}
else
{
string msg = await res.Content.ReadAsStringAsync();
Console.WriteLine(msg);
throw new Exception(msg);
}
}
protected async Task<TReturn> PostAsync<TReturn, TRequest>(string relativeUri, TRequest request)
{
HttpResponseMessage res = await Http.PostAsJsonAsync<TRequest>($"{BaseRoute}/{relativeUri}", request);
if (res.IsSuccessStatusCode)
{
return await res.Content.ReadFromJsonAsync<TReturn>();
}
else
{
string msg = await res.Content.ReadAsStringAsync();
Console.WriteLine(msg);
throw new Exception(msg);
}
}
}
}
and then from derived class, we're back to the one-liner
public class MySpecificAPI : ClientAPI
{
public MySpecificAPI(HttpClient http) : base("api/myspecificapi", http) {}
public async Task<IEnumerable<MyClass>> GetMyClassAsync(int ownerId)
{
try
{
return GetAsync<IEnumerable<MyClass>>($"apiMethodName?ownerId={ownerId}");
}
catch (Exception e)
{
// Deal with exception
}
}
// repeat for post
}
Having encountered a valid scenario where the WebAPI returns null, the line:
return await res.Content.ReadFromJsonAsync<TReturn>();
will throw a Json Deserialization error.
To address this, we need to detect NoContent response (204) and handle accordingly:
if (res.StatusCode == HttpStatusCode.NoContent)
return default(TReturn);
else if (res.IsSuccessStatusCode)
return await res.Content.ReadFromJsonAsync<TReturn>();
I just found out that .NET 5.0 actually added the StatusCode
Property to the HttpRequestException
class!
https://github.com/dotnet/runtime/pull/32455
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httprequestexception.statuscode?view=net-5.0
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With