ASP.NET Core 2 中的依赖注入引发异常

Posted

技术标签:

【中文标题】ASP.NET Core 2 中的依赖注入引发异常【英文标题】:Dependency injection in ASP.NET Core 2 throws exception 【发布时间】:2017-10-26 02:52:30 【问题描述】:

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

未处理的异常:System.Exception:无法为“Communicator.Backend.Startup”类型的方法“Configure”的参数“dbContext”解析“Communicator.Backend.Data.CommunicatorContext”类型的服务。 ---> System.InvalidOperationException:无法从根提供程序解析范围服务“Communicator.Backend.Data.CommunicatorContext”。

如果我尝试接收其他实例,也会引发此异常。

未处理的异常:System.Exception:无法解析“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);

【问题讨论】:

【参考方案1】:

引用文档

Services Available in Startup

ASP.NET Core 依赖注入提供应用服务 应用程序的启动。您可以通过包括以下内容来请求这些服务 适当的接口作为Startup 类的参数 构造函数或其ConfigureConfigureServices 方法之一。

按顺序查看Startup 类中的每个方法 它们被调用时,可能会请求以下服务 参数:

在构造函数中:IHostingEnvironmentILoggerFactoryConfigureServices方法中:IServiceCollectionConfigure方法中:IApplicationBuilderIHostingEnvironmentILoggerFactory, IApplicationLifetime

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

...CommunicatorContext dbContext, ILdapService ldapService) 

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

    修改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();
    
    

    修改ConfigureServices 方法以返回IServiceProvider,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);
    
    

【讨论】:

谢谢,这解决了我的问题。如果我可以添加更多,在测试了两个选项后,我发现我仍然需要将ConfigureServices从返回void(默认)修改为返回IServiceProvider,以便第二个选项起作用。 遇到了类似的问题。让ConfigureServices() 单独返回IServiceProvider 使得通过IApplicationBuilder.ApplicationServices.GetService&lt;T&gt;() 解决我的依赖关系成为可能。我不需要将IServiceProvider 注入Configure() 方法。【参考方案2】:

NKosi 的解决方案有效,因为通过自己调用services.BuildServiceProvider() 而不使用参数,您不会传递validateScopes。由于禁用了此验证,因此不会引发异常。这并不意味着问题不存在。

EF Core DbContext 已使用范围生活方式注册。在 ASP 原生 DI 中,容器作用域连接到 IServiceProvider 的实例。通常,当您从控制器中使用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 中运行而无需任何更改,因为将为配置方法 https://github.com/aspnet/Hosting/pull/1106 创建 RTM 范围内的服务提供者。

【讨论】:

【参考方案3】:

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

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

  // ...

这要容易得多,感谢#1106 的改进。

【讨论】:

这是解决问题的自然方法。谢谢。 使用 2.0 RTM 的最佳方式【参考方案4】:
.UseDefaultServiceProvider(options => 
            options.ValidateScopes = false)

.UseStartup&lt;Startup&gt;()之后的Program.cs中添加这个

为我工作

Documentation Here

【讨论】:

谢谢。也适用于 Asp.Net Core 2.1 Api App 。 ValidateScopes 实际上在做什么?【参考方案5】:

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

var scopeFactory = ApplicationServices.GetService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())

    var dbContext = scope.ServiceProvider.GetService<CommunicatorDbContext>();
    DbInitializer.Initializer(dbContext, ldapService);

尽管如 Slack 上所述,不要这样做 ;-)

【讨论】:

这有什么问题?为什么你建议不要这样做。我现在有这个:using (var scope = app.ApplicationServices.CreateScope()) scope.ServiceProvider.GetService&lt;IInitDatabase&gt;().InitAsync().Wait(); 我应该以其他方式执行此操作吗? 在 Slack 上提到了什么?为什么不应该这样做? @krzysztof-branicki 的回答对此表示赞同,并且已在几个月前发布,您为什么不将其纳入您的回答中?

以上是关于ASP.NET Core 2 中的依赖注入引发异常的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Core 依赖注入基本用法

ASP.NET Core中的依赖注入:控制反转(IoC)

如何解决 ASP.NET Core 中的依赖问题

抽象类中的依赖注入,ASP.NET Core

ASP.Net Core 使用啥依赖注入框架?

ASP.NET Core RC2 中的依赖注入失败