Why can't I inject a service that requires an httpclient an an EF context in a controller that requires the same context

asp.net-core c# entity-framework-core

Question

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.

1
4
10/12/2018 3:59:46 PM

Popular Answer

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
}
1
8/1/2019 4:10:05 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow