Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency injection in ASP.NET Core 2 throws exception

I receive following exception when I try to use custom DbContext in Configure method in Startup.cs file. I use ASP.NET Core in version 2.0.0-preview1-005977

Unhandled 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 and Configure 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);
}
like image 942
M. Galczynski Avatar asked May 25 '17 12:05

M. Galczynski


3 Answers

Quoting documentation

Services Available in Startup

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 its Configure or ConfigureServices 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:

  1. 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(); } 
  2. Modify the ConfigureServices method to return IServiceProvider, Configure method to take a IServiceProvider 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); } 
like image 71
Nkosi Avatar answered Sep 22 '22 02:09

Nkosi


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.

like image 25
Krzysztof Branicki Avatar answered Sep 20 '22 02:09

Krzysztof Branicki


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.

like image 21
Nate Barbettini Avatar answered Sep 24 '22 02:09

Nate Barbettini