C
C#16mo ago
Thinker

✅ Best practice when using HttpClient in a class

I have a class which essentially acts as a wrapper over an HttpClient as an abstraction for a web API. This class has a field private readonly HttpClient client; which is currently set through the constructor of the class, where I'm just kind of expecting the consuming code to have configured the base address and any other configuration in regards to the client itself. However, I don't know if this is considered good practice, or if there's some other way I'm not aware of. This class should also be usable with DI, so I don't know if I instead should have an IHttpClientFactory in the constructor or something instead.
43 Replies
Thinker
Thinker16mo ago
Tl;dr, I have this
public sealed class ApiWrapper
{
private readonly HttpClient client;

public ApiWrapper(HttpClient client)
{
this.client = client;
}

// ...
}
public sealed class ApiWrapper
{
private readonly HttpClient client;

public ApiWrapper(HttpClient client)
{
this.client = client;
}

// ...
}
Is this bad?
undisputed world champions
as long as you don't register ApiWrapper as singleton (and recreate it somewhat regularly), i guess this is alright you can use services.AddHttpClient<ApiWrapper>(...) to configure the client and register ApiWrapper as transient i think ms calls this usage "typed client": https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory#typed-clients
Thinker
Thinker16mo ago
right, nice In what package exactly is AddHttpClient defined? I can't find it nvm, found it
Henkypenky
Henkypenky16mo ago
that is IHttpClientFactory specficically TypedClient AddHttpClient<ApiWrapper>() then just DI and consume within the class
Thinker
Thinker16mo ago
Actually, I don't think this will work if the wrapper is transient, because my API relies on state in the client, so I don't think recreating it for every request will work.
Henkypenky
Henkypenky16mo ago
you can set the settings specifically for that implementation <T> it doesn't matter, ApiWrapper doesn't manage the HttpClients its just a dependency btw typedclients are transient aswell so there is nothing wrong HttpClient might get created every time, but the Handler is reused because the IHttpClientFactory persists and you get all the benefits of creation/disposal/configurations from the service provider
Thinker
Thinker16mo ago
Well I don't want the HttpClient to get created every time
Henkypenky
Henkypenky16mo ago
then don't use harold
Thinker
Thinker16mo ago
sure I guess
undisputed world champions
sry i thought your ApiWrapper is a thin wrapper for the api. basically just defining convenient functions that call the httpclient-functions. have you checked out the "Basic usages" and "Named clients" in the link i posted? you can basically call _httpClientFactory.CreateClient(...); on a IHttpClientFactory to get the HttpClient, when you need it
Henkypenky
Henkypenky16mo ago
you can probably use it inject it through DI and use the "basic usage" where you create a client and keep that client through the life of the apiwrapper life
Thinker
Thinker16mo ago
eh, I'll just add the wrapper as a singleton
Henkypenky
Henkypenky16mo ago
but you have to do it in the DI which is not that cool but i know that you know what you are doing so whatever xd
undisputed world champions
the IHttpClientFactory will take care of caching the httpclient/handler for you and recreate them appropriately
Thinker
Thinker16mo ago
I have no idea what I'm doing Smadge
Henkypenky
Henkypenky16mo ago
yes you do xd
Thinker
Thinker16mo ago
So I should use IHttpClientFactory?
Henkypenky
Henkypenky16mo ago
let's go again do you want a new client each time
Thinker
Thinker16mo ago
So like, thing is that I'm relying on the client storing a cookie for the user being signed in, so I need that info to be retained across requests.
Henkypenky
Henkypenky16mo ago
the factory takes care of that for you
undisputed world champions
well if it's some kind of long running application, yes if it's just a cli command or something like that you don't have to worry about it that much
Thinker
Thinker16mo ago
okay, nice It's a Blazor WASM app
Henkypenky
Henkypenky16mo ago
make sure you do not tell the handler to disable cookies oh wait WASM runs in sandbox you can't manipulate cookies https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.usecookies?view=net-8.0 this doesn't work on WASM
Thinker
Thinker16mo ago
So... I can't do this?
Henkypenky
Henkypenky16mo ago
you will probably have to create a delegating handler and override sendasync then create a custom service to add/remove headers then add the DelegatingHandler to that implementation of HttpClient which is what you should be doing, the cookies of IHttpClientFactory are automatic and shared through CookieContainer which you might not want remember that blazor WASM uses Microsoft.Extensions.Http
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});
Severity Code Description Project File Line Suppression State Warning CA1416 This call site is reachable on all platforms. 'HttpClientHandler.UseCookies' is unsupported on: 'browser'. BlazorApp15 check this: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-7.0#outgoing-request-middleware that would be your DelegatingHandler which you then can add to the config of your HttpClient they are transient so don't worry about them they just do stuff in the middleware of http requests and to be fair, you should use them like 99% of the time even if you can use cookies, i wouldn't let the Factory manage them automatically recipe for disaster
Thinker
Thinker16mo ago
so...
Henkypenky
Henkypenky16mo ago
use DelegatingHandlers and typedclients
Thinker
Thinker16mo ago
So I need to make a type deriving from DelegatingHandler?
Henkypenky
Henkypenky16mo ago
just make a class that derives from it yeah and override SendAsync which is the base of all your fancy http request methods so everything will be hit also since you use TypedClients you can specify which implementation uses what DelegatingHandler and which doesn't so you can have one implementation that you do stuff in the middleware and one that does not
Thinker
Thinker16mo ago
What do I need to do in SendAsync then?
Henkypenky
Henkypenky16mo ago
whatever you want check for headers add headers log cache error handling you name it
Thinker
Thinker16mo ago
yeah but what do I need to do to get cookies to work then?
Henkypenky
Henkypenky16mo ago
if you can explain to me what you need to do maybe i can point you in the right direction
Thinker
Thinker16mo ago
When I call an endpoint in my API to log the user in, the API returns a login cookie, and that cookie should be retained throughout the rest of the app.
Henkypenky
Henkypenky16mo ago
okay what about determining what user is making the request and attach the cookie in the middleware
Thinker
Thinker16mo ago
where do I get the cookie from?
Henkypenky
Henkypenky16mo ago
database? MemoryCache? whatever works for you
Thinker
Thinker16mo ago
Except the API endpoint adds the cookie to the client
Henkypenky
Henkypenky16mo ago
wait i think i misunderstood what you want blazor does have cookies, maybe i expressed myself wrong, what doesn't work is the cookie sharing of IHttpClientFactory it's a sandbox try this https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.webassembly.http.webassemblyhttprequestmessageextensions.setbrowserrequestcredentials?view=aspnetcore-7.0
public class MyHttpClientHandler : HttpClientHandler {

protected override async Task<HttpResponseMessage>
SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken) {
request.SetBrowserRequestCredentials(
BrowserRequestCredentials.Include);

return await base.SendAsync(request, cancellationToken);
}
}
public class MyHttpClientHandler : HttpClientHandler {

protected override async Task<HttpResponseMessage>
SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken) {
request.SetBrowserRequestCredentials(
BrowserRequestCredentials.Include);

return await base.SendAsync(request, cancellationToken);
}
}
that way you get all the cookies from the client
Thinker
Thinker16mo ago
How do I make the HttpClient I add through AddHttpClient use this handler? Okay nvm, changed it to a DelegatingHandler
Henkypenky
Henkypenky16mo ago
yeah if you
builder.Services.AddHttpClient<T>()
.AddHttpMessageHandler<YourCustomHandler>();
builder.Services.AddHttpClient<T>()
.AddHttpMessageHandler<YourCustomHandler>();
every typed client request will go through that custom middleware
Thinker
Thinker16mo ago
yeah, I figured it out The entire thing works now catok
Henkypenky
Henkypenky16mo ago
owo