Как настроить простой контейнер инжектора и lifestylse в веб-приложении MVC с помощью WebAPI, WCF, SignalR и фоновой задачи

Простая документация по инжекторам дает отличные примеры того, как настроить контейнер для WebRequest, Web API, WCF,... но примеры специфичны для одной технологии/образа жизни за раз. Наше веб-приложение использует большинство из них вместе! Мне непонятно, как настроить контейнер для работы с несколькими образцами жизни.


Скажем, у меня есть проект MVC с Web API. У меня есть следующие объекты:

  • MyDbContext: мой код сущности первого db-контекста
  • IMyDataProvider, реализованный MyDataProvider: содержит логику запросов и использует MyDbContext
  • MyController: MVC-контроллер, который использует IMyDataProvider
  • MyApiController: контроллер WebApi, который использует IMyDataProvider

Должен ли я создавать и настраивать один контейнер для каждого типа образа жизни?

Когда я регистрирую все, RegisterPerWebRequest<T> работает в обоих типах контроллеров. Это безопасно? Или я столкнулся с трудностями при использовании async/wait в контроллере Web API?

Какова наилучшая конфигурация, когда у меня есть как MVC, так и Web API-контроллеры, которые вводят те же экземпляры?

Должен ли я использовать гибридный образ жизни?


Теперь, чтобы усложнить ситуацию... наше приложение также использует фоновые задачи и SignalR.

Оба они иногда происходят за пределами WebRequest и нуждаются в доступе к тем же объектам, что описаны выше.

Лучшим решением было бы использовать область Lifetime?

Мне нужно создать новый контейнер для этого образа жизни? или я могу повторно использовать/перенастроить свой контейнер MVC/Web API?

Есть ли тройной образ жизни?

Ответ 1

Обычно вам не нужно иметь один контейнер на образ жизни; В общем, вы хотите иметь один экземпляр контейнера для AppDomain. Тем не менее, смешивание веб-API в том же проекте с MVC с архитектурной точки зрения представляет собой ужасную идею IMO (как описано здесь, здесь, и здесь). Поэтому, если вы разделите эти части на свои архитектурные блоки, у вас уже будет меньше проблем.

Но если вы используете MVC и Web API в одном проекте, это в основном означает, что вы всегда будете использовать веб-API. WebApiRequestLifestyle был явно создан для работы:

хорошо как внутри, так и снаружи IIS. т.е. он может функционировать в сам веб-проект API, в котором отсутствует HttpContext.Current. (источник)

В целом, безопасно использовать WebRequestLifestyle в случае, если вы работаете только в IIS, когда у вас нет намерения вращать параллельные операции с помощью ConfigureAwait(false) (что должно быть действительно редким IMO), как объяснено здесь.

Таким образом, в случае, когда вы все еще смешиваете веб-API с MVC в одном проекте, нет причин использовать гибридный образ жизни; вы можете просто использовать один и тот же образ жизни. Для выполнения фоновой обработки вам может понадобиться построить гибридный образ жизни, но для каждого сценария нужен другой гибрид. Однако, гибриды могут быть сложены, и вы можете легко создать "тройной образ жизни", если это необходимо.

Так как вы хотите выполнять фоновую обработку с помощью SignalR, вам нужно решить, в каком типе охватываемый стиль жизни для запуска этих фоновых операций. Самый очевидный образ жизни - LifetimeScopeLifestyle, и это означает, что вы должны сделать свою регистрацию с использованием следующих видов жизни:

var hybridLifestyle = Lifestyle.CreateHybrid(
    lifestyleSelector: () => HttpContext.Current != null,
    trueLifestyle: new WebRequestLifestyle(),
    falseLifestyle: new LifetimeScopeLifestyle());

Тем не менее, срок службы должен быть явно запущен (так же как и область веб-запросов запускается неявно для вас, если вы включили SimpleInjector.Integration.Web.dll в свое веб-приложение). Как это сделать, зависит от вашего дизайна, но этот q/a о SignalR может указывать на вас в правильном направлении.

Ответ 2

Я должен сказать, что я наткнулся на подобный сценарий некоторое время назад, я закончил тем, что поделился своей конфигурацией через свой веб-API и signalR, но вам нужно реализовать собственный стиль для signalR, поскольку он не основан на веб-запросе.

особенно в signalR, вы обнаружите некоторые проблемы обработки зависимостей для каждого веб-запроса в концентраторе, некоторые из которых будут иметь значение null, например httpContext.Current.

Решение:

Вам нужен гибридный образ жизни между WebRequestLifestlye и Lifestyle.Transient, Lifestyle.Singleton или LifetimeScopeLifestyle. В конце концов я закончил использовать шаблон декоратора, вы можете прочитать этот пост и этот другой пост.

мой декоратор

public class CommandLifetimeScopeDecorator<T> : ICommandHandler<T>
    {
        private readonly Func<ICommandHandler<T>> _handlerFactory;
        private readonly Container _container;

        public CommandLifetimeScopeDecorator(
        Func<ICommandHandler<T>> handlerFactory, Container container)
        {
            _handlerFactory = handlerFactory;
            _container = container;
        }

        public void Handle(T command)
        {
            using (_container.BeginLifetimeScope())
            {
                var handler = _handlerFactory(); // resolve scoped dependencies
                handler.Handle(command);
            }
        }

    }

    public interface ICommandHandler<in T>
    {
        void Handle(T command);
    }

Я управлял зависимостями, используя активатор хаба для сигналаR

public class MyHubActivator : IHubActivator
    {
        private readonly Container _container;

        public MyHubActivator(Container container)
        {
            _container = container;
        }

        public IHub Create(HubDescriptor descriptor)
        {
            return _container.GetInstance(descriptor.HubType) as IHub;
        }
    }

составной корневой файл, в котором вы собираетесь обрабатывать свои зависимости

public CompositRoot(Container container)
{
    _container = container;
}
public container Configure()
{
   // _container.Registerall container dependencies
   return _container;
}

затем поделитесь своей составной конфигурацией корня, когда вы загружаете приложение.

var compositRoot = new CompositRoot(simpleInjector.Container); //simple injector instance
compositRoot.Configure();

Для сигналаR

GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => new MyHubActivator(compositRoot));

и вы можете повторно использовать свою конфигурацию среди других проектов!

мои два цента надеюсь, что это поможет!