ASP.NET Core 2 中的依赖注入抛出异常

Dependency injection in ASP.NET Core 2 throws exception

当我尝试在 Startup.cs 文件的 Configure 方法中使用自定义 DbContext 时,我收到以下异常。我在版本 2.0.0-preview1-005977

中使用 ASP.NET Core

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.

当我尝试接收其他实例时也会抛出此异常。

Unhandled Exception: System.Exception: Could not resolve a service of type 'Communicator.Backend.Services.ILdapService' ...

这是我的 ConfigureServicesConfigure 方法。

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);
}

引用文档

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

您正在尝试解决启动期间不可用的服务,

...CommunicatorContext dbContext, ILdapService ldapService) {

这会给你你得到的错误。如果您需要访问这些实现,则需要执行以下操作之一:

  1. 修改 ConfigureServices 方法并从服务集合中访问它们。即

    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. 修改ConfigureServices方法为returnIServiceProvider,Configure方法取一个IServiceProvider,然后在那里解决你的依赖关系。即

    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);
    }
    

NKosi 的解决方案之所以有效,是因为通过不带参数自己调用 services.BuildServiceProvider(),您不会传递 validateScopes。因为此验证被禁用,所以不会抛出异常。这并不意味着问题不存在。

EF Core DbContext 注册了一种有范围的生活方式。在 ASP 本机 DI 中,容器作用域连接到 IServiceProvider 的实例。通常,当您从 Controller 使用 DbContext 时没有问题,因为 ASP 为每个请求创建一个新范围(IServiceProvider 的新实例),然后使用它来解决此范围内的所有问题要求。但是,在应用程序启动期间,您没有请求范围。您有一个 IServiceProvider 的实例,它没有作用域(或者换句话说,在根作用域中)。这意味着您应该自己创建一个范围。你可以这样做:

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
}

ConfigureServices方法可以保持不变。

编辑

您的解决方案无需任何更改即可在 2.0.0 RTM 中运行,因为在 RTM 中将为 Configure 方法创建范围内的服务提供程序 https://github.com/aspnet/Hosting/pull/1106

.UseDefaultServiceProvider(options => 
            options.ValidateScopes = false)

.UseStartup<Startup>()

之后的 Program.cs 中添加这个


适合我

Documentation Here

或者,您可以在 Configure 方法中创建一个服务范围:

var scopeFactory = ApplicationServices.GetService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
    var dbContext = scope.ServiceProvider.GetService<CommunicatorDbContext>();
    DbInitializer.Initializer(dbContext, ldapService);
}

尽管如 Slack 中所述,请勿这样做 ;-)

在 ASP.NET Core 2.0 和更新版本中,您可以简单地将所需的作用域服务注入 Configure 构造函数,就像您最初尝试做的那样:

public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    ILoggerFactory loggerFactory,
    CommunicatorContext dbContext,
    ILdapService ldapService)
{
  // ...
}

由于 #1106 中的改进,这要容易得多。