То, что вы видите происходящим, связано с этой проблемой .Короче говоря, Web API вызовет реализацию по умолчанию IHttpControllerActivator
, чтобы запросить новый экземпляр контроллера.Этот экземпляр будет вызывать ваш DependencyResolver.GetService
метод.Этот метод перенаправит вызов в метод MS.DI GetService
.Однако, поскольку вы не зарегистрировали свои контроллеры в контейнере MS.DI , он вернет null
.Это заставит по умолчанию IHttpControllerActivator
попытаться создать контроллер с помощью отражения, но для этого требуется конструктор по умолчанию.Так как у контроллера его нет, это приводит к довольно загадочному сообщению об исключении.
Поэтому быстрое решение состоит в том, чтобы зарегистрировать ваши контроллеры, например:
services.AddTransient<TestController>();
Это, однако,, только частично решит вашу проблему, потому что ваша IDependencyResolver
реализация не работает .Он плохо работает, потому что может показаться, что сначала он работает, но приведет к утечкам памяти, потому что вы всегда разрешаете из корневого контейнера, а не из области видимости.Это приведет к тому, что ваши разрешенные экземпляры контроллера (и другие одноразовые переходные компоненты) останутся ссылочными в течение всего срока службы вашего приложения.
Чтобы исправить это, вы должны изменить реализацию IDependencyResolver
на следующее:
public class DependencyResolver : IDependencyResolver
{
private readonly IServiceProvider provider;
private readonly IServiceScope scope;
public DependencyResolver(ServiceProvider provider) => this.provider = provider;
internal DependencyResolver(IServiceScope scope)
{
this.provider = scope.ServiceProvider;
this.scope = scope;
}
public IDependencyScope BeginScope() =>
new DependencyResolver(provider.CreateScope());
public object GetService(Type serviceType) => provider.GetService(serviceType);
public IEnumerable<object> GetServices(Type type) => provider.GetServices(type);
public void Dispose() => scope?.Dispose();
}
Эта реализация гарантирует, что новый IServiceScope
создается при каждом веб-запросе, а службы всегда разрешаются из запроса;не из корня IServiceProvider
.
Хотя это решит ваши проблемы, другая реализация все еще может быть полезной.
Контракт IDependencyResolver
проблематичен, поскольку он вынужден возвращать null
когда вызов GetService
не приводит к правильному разрешению регистрации.Это означает, что вы получите эти досадные ошибки «Убедитесь, что у контроллера есть открытый конструктор без параметров», когда вы забудете зарегистрировать свои контроллеры.
Поэтому гораздо проще создать пользовательский IHttpControllerActivator
вместо.В этом случае вы можете вызвать GetRequiredService
, который никогда не вернет null
:
public class MsDiHttpControllerActivator : IHttpControllerActivator
{
private readonly ServiceProvider provider;
public MsDiHttpControllerActivator(ServiceProvider provider) =>
this.provider = provider;
public IHttpController Create(
HttpRequestMessage request, HttpControllerDescriptor d, Type controllerType)
{
IServiceScope scope = this.provider.CreateScope();
request.RegisterForDispose(scope); // disposes scope when request ends
return (IHttpController)scope.ServiceProvider.GetRequiredService(controllerType);
}
}
Эту реализацию MsDiHttpControllerActivator
можно добавить в конвейер Web API следующим образом:
GlobalConfiguration.Configuration.Services
.Replace(typeof(IHttpControllerActivator),
new MsDiHttpControllerActivator(services.BuildServiceProvider(true)));
Это устраняет необходимость иметь IDependencyResolver
реализацию.Вам все еще нужно зарегистрировать свои контроллеры, хотя:
services.AddTransient<TestController>();
Также обратите внимание, что я изменил это:
services.BuildServiceProvider()
На это:
services.BuildServiceProvider(true)
Это действительно важное изменение ;он защищает вас (в некоторой степени) от зависимостей , которые являются одной из основных проблем при использовании DI-контейнеров.По какой-то непонятной причине перегрузка BuildServiceProvider()
по умолчанию принимает значение false
, что означает, что она не будет проверять ваши области.