Getting Error Messages and Status Codes from Typed HttpClients

Full source code here.

I have been using HttpClientFactory for a while and have generally preferred using named clients over typed clients. With a named client you get the HttpClient just before you are going to use it, and you have full access to the HttpResponse at the point you make the call to the remote service.

var httpClient = _httpClientFactory.CreateClient("InvoiceApi");
HttpResponseMessage response = await httpClient.GetAsync(requestEndpoint);

This is great when things go wrong because you can check the HttpStatus code and the ReasonPhrase, any message, and then take appropriate action.

If you are using a typed client, it might look something like this -

 1public class InvoiceClient
 2{
 3	private readonly HttpClient _client;
 4
 5	public InvoiceClient(HttpClient client)
 6	{
 7		_client = client;
 8	}
 9	
10	public async Task<Invoice> GetInvoice(int orderId)
11	{
12		var response = await _client.GetAsync($"invoice/{orderId}");
13		Invoice invoice = await response.Content.ReadAsAsync<Invoice>();
14		return invoice;
15	}
16}

And then you use it -

var invoice = await invoiceClient.GetOrder(id);

If the request worked, you are fine, but if it didn’t you and got an error, then the deseriaization will fail and an exception will be thrown, but that exception won’t tell you very much about the cause of the original failure.

Returning a typed response

Here’s the alternative. I use a typed response that includes the HttpStatusCode, the ReasonPhrase and the content I actually want, in this case the Invoice.

1public class TypedResponse<T>
2{
3	public T Content { get; set; }
4	public HttpStatusCode HttpStatusCode { get; set; }
5	public string ReasonPhrase { get; set; }
6	public string Message { get; set; }
7}

Here’s how to change the GetInvoice method in the InvoiceClient.

 1public async Task<TypedResponse<Invoice>> GetInvoice(int orderId)
 2{
 3    var response = await _client.GetAsync($"invoice/{orderId}");
 4    if (response.IsSuccessStatusCode)
 5    {
 6        Invoice invoice = await response.Content.ReadAsAsync<Invoice>();
 7        return new TypedResponse<Invoice> { Content = invoice, HttpStatusCode = response.StatusCode };
 8    }
 9    
10    return new TypedResponse<Invoice>{ HttpStatusCode = response.StatusCode, Message = await response.Content.ReadAsStringAsync(), ReasonPhrase = response.ReasonPhrase };
11}

And finally, how to use it in the controller.

1var invoiceAndStatus = await _invoiceClient.GetInvoice(orderId);
2
3if (invoiceAndStatus.Content != null)
4{
5    order.Cost = invoiceAndStatus.Content.Cost;
6    return Ok(order);
7}
8return StatusCode((int)invoiceAndStatus.HttpStatusCode, invoiceAndStatus.Message);

Run the code, it will open your browser to http://localhost:5000/api/order/2. The OrderController will call the InvoiceController, the first request will fail, but you will see the error message from the InvoiceController.

Hit F5 in the browser to make another request to the OrderController, again this calls the InvoiceController, but this time the response is a success.

Full source code here.

comments powered by Disqus

Related