I have a controller that needs a service so I inject it. The service needs an HttpClient
so I inject it. They both need the EF context.
I do add the HttpClient
at Startup for the service. However I have the below error.
In Startup
:
services.AddHttpClient<IMyService, MyService>();
In MyService
class
private readonly HttpClient _httpClient;
private readonly ReadContext _readContext;
public MyService(HttpClient httpClient, ReadContext readContext)
{
_httpClient = httpClient;
_readContext = readContext;
}
In my controller:
private readonly IMyService _myService;
public MyController(ReadContext ctx, IMyService myService)
{
_ctx = ctx;
_myService = myService;
}
The error:
System.InvalidOperationException: Cannot resolve scoped service 'MyApp.Backend.ReadContext' from root provider.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.Microsoft.Extensions.DependencyInjection.ServiceLookup.IServiceProviderEngineCallback.OnResolve(Type serviceType, IServiceScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
at lambda_method(Closure , IServiceProvider , Object[] )
at Microsoft.Extensions.Http.DefaultTypedHttpClientFactory`1.CreateClient(HttpClient httpClient)
Not that when the service is injected in a controller that doesn't require ReadContext
, this works. There seems to be some resolution loop here.
UPDATE
From comments, I understand that AddHttpClient<T>
creates an instance of T
with the same lifetime as the HttpClient
created and managed by aspnet core which is longer than the DbContext
lifetime which is the lifetime of the actual request. Fair enough. Then how do I inject an HttpClient
into a transient service?
UPDATE2
As per that blog I can inject an instance of IHttpClientFactory
in MyService
instead using services.AddHttpClient();
. Indeed the error goes away. I'll have to do some reading to understand the difference beetween asking the factory for an instance of HttpClient
or injecting one.
Register MyService
as scoped:
services.AddScoped<IMyService, MyService>();
And, then just register your HttpClient
with MyService
:
services.AddHttpClient<MyService>();
The way you're doing it now, MyService
is being created in singleton scope, which makes it impossible to inject scoped dependencies like your context.
Alternatively, you can instead inject IServiceProvider
and use the service-locator anti-pattern to get your context. It's called an anti-pattern for a reason, so this is not the best way to go about things, but if your service is singleton-scoped, there's no other way:
public class MyService : IMyService
{
private readonly HttpClient _client;
private readonly IServiceProvider _services;
public MyService(HttpClient client, IServiceProvider services)
{
_client = client;
_services = services;
}
}
Then, later:
using (var scope = _services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ReadContext>();
// do something
}