Httpclient and exception handling

Solarion

Honorary Master
Joined
Nov 14, 2012
Messages
28,051
Reaction score
17,804
Hey guys. This is something I'm coming back to because in the past I've been a bit lazy and simply let the exceptions get logged where the httpclient makes the calls.

I'm wanting to implement something more user friendly. I have created a generic httpclient wrapper in a shared Infrastructure library. Then I'm injecting that into second reusable utility class which handles all the calls, dto resolving where needed and simply returns the basic nulls, empty lists, bools and int for the various calls. I've made it reusable because several projects need to call the API. Whether it's a good plan or not I'm open to suggestions.

What I'm not sure about is what to do with the exceptions like 404 or even just the response codes in general. Should I hand then in the utility classes, the wrapper class or in the calling project itself?

If in the calling project, I end up with a case of var user = response.data(This is using a Result wrapper)

Then for checking the response it will be a case of:
If(response.success)
{
user = response.data;
}
else
{
//not sure what to do here, throw it or log it?
}

I'm typing from a phone so it looks crap sorry.

I am away from my pc atm but will certainly share the code and implementation tomorrow. I just want to get a feel for what you guys do. Your feedback is appreciated as this is something I've never quite pinned down.
 
HttpClient already is the generic wrapper around HTTP

What I do is create specific clients. e.g GitHubApiClient. This has an HttpClient injected into it, and is configured during “ConfigureServices”.

The specific clients have specific methods, GitHubApiClient#GetRepositories()

These methods will know how to build requests to the specific endpoints, and unwrap them into DTO.
It will know the expected responses for success and failure. They also call Reponse#IsSuccessStatusCode, and return appropriate errors (as discrimated unions do not exist yet in C#, I return “ErrorOr<>”)

By doing this, you can easily then swop out for a BitbucketApiClient instead.

If your application code has to handle any HTTP responses, this then is just becomes a mess
 
@_kabal_

That's great. I like it. The Idea of having the generic wrapper just didn't fully sit well, felt like too many abstractions going on. I'll inject the httpclient rather. I am at home from early tomorrow and will quickly refractor what I have to fit your suggestions right from the configure services, the client and then making a get and update requests. I appreciate the feedback thank you. Be back tomorrow and will post results.
 
If you use AddHttpClient, then the HttpClientFactory will make sure that the service gets their own HttpClient

C#:
services.AddHttpClient<SomeApiClient>((provider, httpClient) =>
{
     var someApiClientSettings = provider.GetRequiredService<IOptions<SomeApiClientSettings>>().Value;
     httpClient.DefaultRequestHeaders.Add("Token", someApiClientSettings.AuthorizationToken);
     httpClient.BaseAddress = new Uri(someApiClientSettings.BaseUrl);
}).AddTransientHttpErrorPolicy(policyBuilder =>
     policyBuilder.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(300)));

services.AddHttpClient<AnotherApiClient>((provider, httpClient) =>
{
     var anotherApiClientSettings = provider.GetRequiredService<IOptions<AnotherApiClientSettings>>().Value;
     httpClient.DefaultRequestHeaders.Add("X-API-KEY", anotherApiClientSettings.ApiKey);
     httpClient.BaseAddress = new Uri(anotherApiClientSettings.BaseUrl);
}).AddTransientHttpErrorPolicy(policyBuilder =>
     policyBuilder.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(300)));[

This means that the HttpClient that `SomeApiClient` gets will be different to the one that `AnotherApiClient` gets, so you dont get conflicts with e.g. default headers, or default base URL.

setting up the HttpClient defaults here, means less things to worry about in your actual implementation.

The bonus of `AddHttpClient` is that it makes automatic retry/backoff/whatever policy you want, with Polly (Microsoft.Extensions.Http.Polly), a breeze
 
Last edited:
If you use AddHttpClient, then the HttpClientFactory will make sure that the service gets their own HttpClient

C#:
services.AddHttpClient<SomeApiClient>((provider, httpClient) =>
{
     var someApiClientSettings = provider.GetRequiredService<IOptions<SomeApiClientSettings>>().Value;
     httpClient.DefaultRequestHeaders.Add("Token", someApiClientSettings.AuthorizationToken);
     httpClient.BaseAddress = new Uri(someApiClientSettings.BaseUrl);
}).AddTransientHttpErrorPolicy(policyBuilder =>
     policyBuilder.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(300)));

services.AddHttpClient<AnotherApiClient>((provider, httpClient) =>
{
     var anotherApiClientSettings = provider.GetRequiredService<IOptions<AnotherApiClientSettings>>().Value;
     httpClient.DefaultRequestHeaders.Add("X-API-KEY", anotherApiClientSettings.ApiKey);
     httpClient.BaseAddress = new Uri(anotherApiClientSettings.BaseUrl);
}).AddTransientHttpErrorPolicy(policyBuilder =>
     policyBuilder.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(300)));[

This means that the HttpClient that `SomeApiClient` gets will be different to the one that `AnotherApiClient` gets, so you dont get conflicts with e.g. default headers, or default base URL.

setting up the HttpClient defaults here, means less things to worry about in your actual implementation.

The bonus of `AddHttpClient` is that it makes automatic retry/backoff/whatever policy you want, with Polly (Microsoft.Extensions.Http.Polly), a breeze

Thanks a lot @_kabal_

I got a bit sidetracked with other things today. Have been refactoring what I have now and will post it later possibly after load shedding.
 
HttpClient already is the generic wrapper around HTTP

What I do is create specific clients. e.g GitHubApiClient. This has an HttpClient injected into it, and is configured during “ConfigureServices”.

The specific clients have specific methods, GitHubApiClient#GetRepositories()

These methods will know how to build requests to the specific endpoints, and unwrap them into DTO.
It will know the expected responses for success and failure. They also call Reponse#IsSuccessStatusCode, and return appropriate errors (as discrimated unions do not exist yet in C#, I return “ErrorOr<>”)

By doing this, you can easily then swop out for a BitbucketApiClient instead.

If your application code has to handle any HTTP responses, this then is just becomes a mess
Yes agreed your app or consuming class should not be aware at all that there is HTTP involved
 
Top
Sign up to the MyBroadband newsletter
X