-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Make AddHttpClient() inject an ITypedHttpClientFactory into instantiated class #64034
Comments
Tagging subscribers to this area: @dotnet/ncl Issue DetailsBackground and motivationWe 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 One fix is to make API ProposalI propose that calling The advantage is that service singleton services that wrap typed clients could remain singletons instead of needing to be reconfigured to be transient. API UsageUserService.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 DesignsNo response RisksNo response
|
Tagging subscribers to this area: @dotnet/ncl Issue DetailsBackground and motivationWe 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 One fix is to make API ProposalI propose that calling The advantage is that service singleton services that wrap typed clients could remain singletons instead of needing to be reconfigured to be transient. API UsageUserService.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 DesignsNo response RisksNo response
|
Thanks for the suggestion @Bosch-Eli-Black!
While it should be possible to extend P.S.: It would be even better to have |
@CarnaViire why's that? |
It depends on what you call a Typed client. I would say Typed client is a user-defined transient service which would have an HttpClient automatically injected in constructor. That injected HttpClient - yes, under the hood it is a named client (while that's an implementation detail). But the Typed client can have and usually has additional dependencies beside HttpClient. And in case a Typed client is resolved within a scope, all it's dependencies should be resolved within that scope too. But you cannon capture a scope from within a singleton service. That's why singleton @stijnherreman does it make sense? Should I elaborate further? |
That makes sense yes. I assumed the hypothetical |
What's the actual status of this one, as I'm facing the same performance issue? As in the initial request, I need it to happen only once, and not every time, as at this point I'm paying a huge price when comes to initializing a new instance of the service. |
@adcorduneanu if the problem you are facing is the one where you need to inject Typed clients into a singleton, you can consider a workaround with services.AddHttpClient<MyTypedClient>()
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new SocketsHttpHandler()
{
PooledConnectionLifetime = TimeSpan.FromMinutes(2)
};
})
.SetHandlerLifetime(Timeout.InfiniteTimeSpan); // Disable rotation, as it is handled by PooledConnectionLifetime You can read a bit more in the docs and you can also check out this gist. |
An alternative 'workaround' suggestion would be to register a I've put a gist up based on @CarnaViire gist above. For me it seems (maybe?) cleaner to keep the registration of the typed client cleaner, and leaves it down to the user of the typed client whether they are also transient and take a dependency on the typed client directly, or need to be singleton and use as example below. Its similar in concept to approach here, but without UserRepo.cs public class UserRepo : IUserRepo
{
private readonly Func<IUserService> _userServiceFunc;
/// <summary>
/// Class that allows for managing target lists including CRUD and commands
/// </summary>
public UserRepo(Func<IUserService> userServiceFunc)
{
_userServiceFunc = userServiceFunc;
}
public DoSomething()
{
# Create a IUserService typed http client for each request
_ = await this._userServiceFunc().Example();
}
} Startup.cs # all the code in original example plus...
services.AddSingleton<Func<IUserService>>(sp => () => sp.GetRequiredService<IUserService>()); It could be all that is needed to be considered a 'fix' is Any thoughts or feedback? |
@CarnaViire Would the workaround using |
@chunick the workaround would work for scoped and transient services as well. The change essentially makes the handler to have a "singleton" DI lifetime. Meaning that the same handler instance will be reused in all instances of typed clients of a specific type. |
Background and motivation
We currently have some code like this:
UserService.cs
UserRepo.cs
Startup.cs
This is a problem, because while
IUserService
is transient,IUserRepo
is a singleton, so theHttpClient
that's passed toIUserService
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 injectHttpClient
but should also inject a new interface,ITypedHttpClientFactory
. The user could then useITypedHttpClientFactory
to create new instances ofHttpClient
that have the settings that were configured viaAddHttpClient()
.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
UserRepo.cs (Same as above – no changes)
Startup.cs (Same as above – no changes)
Alternative Designs
No response
Risks
No response
The text was updated successfully, but these errors were encountered: