Description
Background and motivation
We currently have some code like this:
UserService.cs
public class UserService : IUserService
{
private readonly HttpClient _httpClient;
public UserService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task Example()
{
await _httpClient.GetAsync("...");
}
}
UserRepo.cs
public class UserRepo : IUserRepo
{
private readonly IUserService _userService;
/// <summary>
/// Class that allows for managing target lists including CRUD and commands
/// </summary>
public Operator(IUserService userService)
{
_userService = userService;
}
}
Startup.cs
services
.AddHttpClient<IUserService, UserService>(httpClient =>
{
httpClient.BaseAddress = new Uri(Configuration["...."], UriKind.Absolute);
})
.ConfigurePrimaryHttpMessageHandler(sp =>
{
return new HttpClientHandler() {
UseProxy = true,
DefaultProxyCredentials = CredentialCache.DefaultCredentials
};
});
services.AddSingleton<IUserRepo, UserRepo>();
This is a problem, because while IUserService
is transient, IUserRepo
is a singleton, so the HttpClient
that's passed to IUserService
will never be refreshed.
One fix is to make IUserRepo
also be transient, but this isn't always ideal.
API Proposal
I propose that calling AddHttpClient()
should no only inject HttpClient
but should also inject a new interface, ITypedHttpClientFactory
. The user could then use ITypedHttpClientFactory
to create new instances of HttpClient
that have the settings that were configured via AddHttpClient()
.
The advantage is that service singleton services that wrap typed clients could remain singletons instead of needing to be reconfigured to be transient.
API Usage
UserService.cs
public class UserService : IUserService
{
private readonly ITypedHttpClientFactory _httpClientFactory;
public UserService(ITypedHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task Example()
{
// This HttpClient will have the BaseUrl, Proxy, etc. that was configured via the AddHttpService() call.
using var httpClient = _httpClientFactory.CreateClient();
await httpClient.GetAsync("...");
}
}
UserRepo.cs (Same as above – no changes)
public class UserRepo : IUserRepo
{
private readonly IUserService _userService;
/// <summary>
/// Class that allows for managing target lists including CRUD and commands
/// </summary>
public Operator(IUserService userService)
{
_userService = userService;
}
}
Startup.cs (Same as above – no changes)
services
.AddHttpClient<IUserService, UserService>(httpClient =>
{
httpClient.BaseAddress = new Uri(Configuration["...."], UriKind.Absolute);
})
.ConfigurePrimaryHttpMessageHandler(sp =>
{
return new HttpClientHandler() {
UseProxy = true,
DefaultProxyCredentials = CredentialCache.DefaultCredentials
};
});
services.AddSingleton<IUserRepo, UserRepo>();
Alternative Designs
No response
Risks
No response