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' ...
这是我的 ConfigureServices
和 Configure
方法。
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);
}
引用文档
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) {
这会给你你得到的错误。如果您需要访问这些实现,则需要执行以下操作之一:
修改 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
方法为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 中添加这个
适合我
或者,您可以在 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 中的改进,这要容易得多。
当我尝试在 Startup.cs
文件的 Configure
方法中使用自定义 DbContext 时,我收到以下异常。我在版本 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.
当我尝试接收其他实例时也会抛出此异常。
Unhandled Exception: System.Exception: Could not resolve a service of type 'Communicator.Backend.Services.ILdapService'
...
这是我的 ConfigureServices
和 Configure
方法。
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);
}
引用文档
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
您正在尝试解决启动期间不可用的服务,
...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
方法为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>()
适合我
或者,您可以在 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 中的改进,这要容易得多。