MVC5 WebAPI и внедрение зависимостей - PullRequest
0 голосов
/ 04 апреля 2019

Попытка сделать некоторые DI на WebAPI2 без сторонних инструментов.
Итак, из некоторых примеров у меня есть пользовательский преобразователь зависимостей (почему нет интегрированного? Странно, даже Microsoft.Extensions.DependencyInjection ничего не предоставляет):

public class DependencyResolver : IDependencyResolver
    {
        protected IServiceProvider _serviceProvider;

        public DependencyResolver(IServiceProvider serviceProvider)
        {
            this._serviceProvider = serviceProvider;
        }

        public IDependencyScope BeginScope()
        {
            return this;
        }

        public void Dispose()
        {

        }

        public object GetService(Type serviceType)
        {
            return this._serviceProvider.GetService(serviceType);
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return this._serviceProvider.GetServices(serviceType);
        }

        public void AddService()
        {

        }
    }

затем создал этот класс:

public class ServiceConfig
    {
        public static void Register(HttpConfiguration config)
        {
            var services = new ServiceCollection();
            services.AddScoped<IMyService, MyServiceClient>();

            var resolver = new DependencyResolver(services.BuildServiceProvider());
            config.DependencyResolver = resolver;
        }

    }

и зарегистрировал его:

protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
            GlobalConfiguration.Configure(ServiceConfig.Register);
        }

Но когда я пытаюсь использовать это:

public class TestController : ApiController
    {
        private IMyService _myService = null;

        public TestController(IMyService myService)
        {
            _myService = myService;
        }

        public void Get()
        {
            _myService.DoWork();
        }
}

Я получаю сообщение об ошибке " Произошла ошибка при попытке создать контроллер типа 'TestController'. Убедитесь, что контроллер имеет открытый конструктор без параметров. ".
Как правильно приготовить это?

1 Ответ

3 голосов
/ 04 апреля 2019

То, что вы видите происходящим, связано с этой проблемой .Короче говоря, 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, что означает, что она не будет проверять ваши области.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...