Dependency injection in ASP.NET Core 2 throws exception
Solution 1
Quoting documentation
ASP.NET Core dependency injection provides application services during an application's startup. You can request these services by including the appropriate interface as a parameter on your
Startup
class's constructor or one of itsConfigure
orConfigureServices
methods.Looking at each method in the
Startup
class in the order in which they are called, the following services may be requested as parameters:
- In the constructor:
IHostingEnvironment
,ILoggerFactory
- In the
ConfigureServices
method:IServiceCollection
- In the
Configure
method:IApplicationBuilder
,IHostingEnvironment
,ILoggerFactory
,IApplicationLifetime
You are trying to resolve services that are not available during startup,
...CommunicatorContext dbContext, ILdapService ldapService) {
which will give you the errors you are getting. If you need access to the implementations then you need to do one of the following:
-
Modify the
ConfigureServices
method and access them there from the service collection. i.e.public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddDbContext<CommunicatorContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddCookieAuthentication(); services.Configure<LdapConfig>(Configuration.GetSection("Ldap")); services.AddScoped<ILdapService, LdapService>(); services.AddMvc(); // Build the intermediate service provider var serviceProvider = services.BuildServiceProvider(); //resolve implementations var dbContext = serviceProvider.GetService<CommunicatorContext>(); var ldapService = serviceProvider.GetService<ILdapService>(); DbInitializer.Initialize(dbContext, ldapService); //return the provider return serviceProvider(); }
-
Modify the
ConfigureServices
method to return IServiceProvider,Configure
method to take aIServiceProvider
and then resolve your dependencies there. i.e.public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddDbContext<CommunicatorContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddCookieAuthentication(); services.Configure<LdapConfig>(Configuration.GetSection("Ldap")); services.AddScoped<ILdapService, LdapService>(); services.AddMvc(); // Build the intermediate service provider then return it return services.BuildServiceProvider(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider serviceProvider) { //...Other code removed for brevity app.UseMvc(); //resolve dependencies var dbContext = serviceProvider.GetService<CommunicatorContext>(); var ldapService = serviceProvider.GetService<ILdapService>(); DbInitializer.Initialize(dbContext, ldapService); }
Solution 2
The solution from NKosi works because by invoking services.BuildServiceProvider()
yourself without parameters you are not passing the validateScopes
. Because this validation is disabled, the exception is not thrown. This doesn't mean that the problem is not there.
EF Core DbContext
is registered with a scoped lifestyle. In ASP native DI, the container scope is connected to instance of IServiceProvider
. Normally, when you use your DbContext
from a Controller there is no problem because ASP creates a new scope (new instance of IServiceProvider
) for each request and then uses it to resolve everything within this request. However during the application startup you don't have request scope. You have an instance of IServiceProvider
that is not scoped (or in other words in root scope). This means you should create a scope yourself. You can do it like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<CommunicatorContext>();
var ldapService = scope.ServiceProvider.GetRequiredService<ILdapService>();
// rest of your code
}
// rest of Configure setup
}
The ConfigureServices
method can remain unchanged.
EDIT
Your solution will work in 2.0.0 RTM without any changes since in RTM scoped service provider will be created for Configure method https://github.com/aspnet/Hosting/pull/1106.
Solution 3
In ASP.NET Core 2.0 and newer you can simply inject the scoped service you need into the Configure
constructor, like you tried to do initially:
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
CommunicatorContext dbContext,
ILdapService ldapService)
{
// ...
}
This is a lot easier, thanks to the improvements in #1106.
Solution 4
.UseDefaultServiceProvider(options =>
options.ValidateScopes = false)
add this in Program.cs after .UseStartup<Startup>()
Works for me
Solution 5
Alternatively, you can create a service scope within your Configure
method:
var scopeFactory = ApplicationServices.GetService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<CommunicatorDbContext>();
DbInitializer.Initializer(dbContext, ldapService);
}
Although, as mentioned on Slack, don't do this ;-)
M. Galczynski
Updated on August 21, 2020Comments
-
M. Galczynski over 3 years
I receive following exception when I try to use custom DbContext in
Configure
method inStartup.cs
file. I use ASP.NET Core in version 2.0.0-preview1-005977Unhandled Exception: System.Exception: Could not resolve a service of type 'Communicator.Backend.Data.CommunicatorContext' for the parameter 'dbContext' of method 'Configure' on type 'Communicator.Backend.Startup'. ---> System.InvalidOperationException: Cannot resolve scoped service 'Communicator.Backend.Data.CommunicatorContext' from root provider.
This exception is also thrown in case when I try to receive other instance.
Unhandled Exception: System.Exception: Could not resolve a service of type 'Communicator.Backend.Services.ILdapService'
...
Here are my
ConfigureServices
andConfigure
methods.public void ConfigureServices(IServiceCollection services) { services.AddDbContext<CommunicatorContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddCookieAuthentication(); services.Configure<LdapConfig>(Configuration.GetSection("Ldap")); services.AddScoped<ILdapService, LdapService>(); services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, CommunicatorContext dbContext, ILdapService ldapService) { app.UseAuthentication(); app.UseWebSockets(); app.Use(async (context, next) => { if (context.Request.Path == "/ws") { if (context.WebSockets.IsWebSocketRequest) { WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); await Echo(context, webSocket); } else { context.Response.StatusCode = 400; } } else { await next(); } }); app.UseMvc(); DbInitializer.Initialize(dbContext, ldapService); }
-
muhihsan almost 7 yearsThanks, this solves my problem. If I can add more, after testing both options, I found that I still need to modify
ConfigureServices
from returningvoid
(by default) to returnIServiceProvider
, in order for the second option to work. -
Ciantic over 6 yearsWhat is wrong with this? Why do you suggest not to do this. I have now this:
using (var scope = app.ApplicationServices.CreateScope()) scope.ServiceProvider.GetService<IInitDatabase>().InitAsync().Wait();
should I do this some other way? -
Sith2021 over 6 yearsIt is the natural way to solve the problem of the question. Thank you.
-
Postlagerkarte over 6 yearsBest way to do it with the 2.0 RTM
-
Erich Brunner almost 6 yearsThanks. Works in Asp.Net Core 2.1 Api App , too. What is that ValidateScopes doing actually?
-
Francesco de Guytenaere over 5 yearsMentioned what on Slack? Why shouldn't this be done? @krzysztof-branicki 's answer has upvotes suggesting this and has been posted months earlier, why haven't you taken this into your answer?
-
Good Night Nerd Pride about 5 yearsHad a similar problem. Making
ConfigureServices()
return theIServiceProvider
alone made it possible to resolve my dependencies viaIApplicationBuilder.ApplicationServices.GetService<T>()
. I didn't need to inject theIServiceProvider
into theConfigure()
method.