Включение зависимостей в ASP.NET Core 2 выбрасывает исключение
Я получать следующее исключение при попытке использовать пользовательские DbContext в Configure
метода в Startup.cs
файле. Я использую ASP.NET Core в версии 2.0.0-preview1-005977
Необработанное исключение: System.Exception: не удалось разрешить службу типа 'Communicator.Backend.Data.CommunicatorContext' для параметра 'dbContext' метода 'Configure' для типа 'Communicator.Backend.Startup'. ---> System.InvalidOperationException: Невозможно разрешить определенную службу "Communicator.Backend.Data.CommunicatorContext" из корневого поставщика.
Это исключение также выдается в случае, когда я пытаюсь получить другой экземпляр.
Необработанное исключение: System.Exception: не удалось разрешить службу типа '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);
}
Ответы
Ответ 1
Цитата документации
Службы, доступные при запуске
Вставка ASP.NET Core для зависимостей обеспечивает услуги приложений во время запуск приложения. Вы можете запросить эти услуги, включив соответствующий интерфейс в качестве параметра в вашем классе Startup
конструктор или один из его методов Configure
или ConfigureServices
.
Рассмотрение каждого метода в классе Startup
в порядке, в котором они вызываются, следующие услуги могут запрашиваться как Параметры:
- В конструкторе:
IHostingEnvironment
, ILoggerFactory
- В методе
ConfigureServices
: IServiceCollection
- В методе
Configure
: 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
, чтобы вернуть метод 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);
}
Ответ 2
Решение от NKosi работает, потому что, вызывая services.BuildServiceProvider()
самостоятельно без параметров, вы не передаете validateScopes
. Поскольку эта проверка отключена, исключение не выдается. Это не значит, что проблема не в этом.
EF Core DbContext
зарегистрирован с ограниченным образом жизни. В собственном DI ASP область контейнера связана с экземпляром 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 без каких-либо изменений, поскольку в RTM будет создан поставщик услуг с заданным значением для метода Configure https://github.com/aspnet/Hosting/pull/1106.
Ответ 3
В ASP.NET Core 2.0 и новее вы можете просто внедрить нужную вам сервисную область в конструктор Configure
, как вы пытались сделать изначально:
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
CommunicatorContext dbContext,
ILdapService ldapService)
{
// ...
}
Это намного проще, благодаря улучшениям в # 1106.
Ответ 4
.UseDefaultServiceProvider(options =>
options.ValidateScopes = false)
добавить это в Program.cs после .UseStartup<Startup>()
Работает для меня
Документация здесь
Ответ 5
В качестве альтернативы вы можете создать область обслуживания в своем методе Configure
:
var scopeFactory = ApplicationServices.GetService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<CommunicatorDbContext>();
DbInitializer.Initializer(dbContext, ldapService);
}
Хотя, как упоминалось в Slack, не делайте этого: -)