Время жизни для каждого запроса для Unity с собственным хостом OWIN WebApi

Я создаю службу WebApi, используя самостоятельный хостинг OWIN (может работать как в консольном режиме, так и в качестве службы Windows - окончательное развертывание будет службой, консольный режим предназначен для отладки/разработки). У меня проблемы с временем жизни каждого запроса для внедрения Unity.

Ранее я создавал другие службы, развернутые как IIS WebApi; Я добавил nuget Unity Mvc вместе с базовым nuget Unity, следуйте инструкциям, и все работает.

В среде собственного размещения OWIN у меня нет проблем с регистрацией типов по умолчанию и регистрацией ContainerControlledLifetimeManager, но я не могу заставить работать PerRequestLifetimeManager. Во-первых, UnityMvcActivator не запускается автоматически на собственном хосте OWIN, как в IIS. Если при этом я явно вызову UnityMvcActivator.Start, попытка разрешения типа, зарегистрированного с помощью PerRequestLifetimeManager, приведет к возникновению InvalidOperationException со следующим сообщением:

PerRequestLifetimeManager можно использовать только в контексте HTTP-запроса. Возможными причинами этой ошибки являются использование диспетчера времени жизни в приложении, отличном от ASP.NET, или использование его в потоке, который не связан с соответствующим контекстом синхронизации.

Может ли PerRequestLifetimeManager работать с собственным хостом OWIN или это зависит от IIS? Если он будет работать под самостоятельным хостом OWIN, как мне заставить UnityMvcActivator правильно запускаться и регистрировать введенные типы? Если PerRequestLifetimeManager не совместим с самостоятельным хостом OWIN, как еще я могу зарегистрировать типы с временем жизни для каждого запроса?

Часть UnityConfig:

    public static void RegisterTypes(IUnityContainer container)
    {
        // NOTE: To load from web.config uncomment the line below.
        // Make sure to add a Unity.Configuration to the using statements.
        // container.LoadConfiguration();

        // TODO: Register your type's mappings here.
        // container.RegisterType<IProductRepository, ProductRepository>();

        // My test registration:
        container.RegisterType<ITestService, TestService>(new PerRequestLifetimeManager());
    }

UnityMvcActivator (как указано в NuGet, за исключением одной строки без комментариев):

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(Accounting.Service.UnityMvcActivator), nameof(Accounting.Service.UnityMvcActivator.Start))]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(Accounting.Service.UnityMvcActivator), nameof(Accounting.Service.UnityMvcActivator.Shutdown))]

namespace Accounting.Service
{
    /// <summary>
    /// Provides the bootstrapping for integrating Unity with ASP.NET MVC.
    /// </summary>
    public static class UnityMvcActivator
    {
        /// <summary>
        /// Integrates Unity when the application starts.
        /// </summary>
        public static void Start() 
        {
            var container = UnityConfig.Container;

            FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
            FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));

            DependencyResolver.SetResolver(new UnityDependencyResolver(container));

            // TODO: Uncomment if you want to use PerRequestLifetimeManager
            Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
        }

        /// <summary>
        /// Disposes the Unity container when the application is shut down.
        /// </summary>
        public static void Shutdown()
        {
            UnityConfig.Container.Dispose();
        }
    }
}

ИЗМЕНИТЬ

Кажется, что MVC PerRequestLifetimeManager не будет работать в контексте, в котором я пытаюсь его использовать, но я разработал то, что я считаю обходным решением с использованием промежуточного программного обеспечения OWIN, следующим образом:

public class PerRequestMiddleware : OwinMiddleware
{
    [ThreadStatic] static Dictionary<Type, object> _instances;

    public PerRequestMiddleware(OwinMiddleware next) : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        _instances = null;

        await Next.Invoke(context);

        if (_instances != null)
        {
            foreach (var disposable in _instances.Values.OfType<IDisposable>())
            {
                disposable.Dispose();
            }
        }
    }

    public static T GetInstance<T>(Func<T> constructor)
    {
        if (_instances == null)
        {
            _instances = new Dictionary<Type, object>();
        }

        var type = typeof(T);

        if (!_instances.TryGetValue(type, out var instance))
        {
            instance = constructor();
            _instances[type] = instance;
        }

        return (T)instance;
    }
}

Вместо регистрации по типу я регистрирую фабрики следующим образом:

public static class UnityConfig
{
    #region Unity Container
    private static Lazy<IUnityContainer> container =
      new Lazy<IUnityContainer>(() =>
      {
          var container = new UnityContainer();
          RegisterTypes(container);
          return container;
      });

    /// <summary>
    /// Configured Unity Container.
    /// </summary>
    public static IUnityContainer Container => container.Value;
    #endregion

    /// <summary>
    /// Registers the type mappings with the Unity container.
    /// </summary>
    /// <param name="container">The unity container to configure.</param>
    /// <remarks>
    /// There is no need to register concrete types such as controllers or
    /// API controllers (unless you want to change the defaults), as Unity
    /// allows resolving a concrete type even if it was not previously
    /// registered.
    /// </remarks>
    public static void RegisterTypes(IUnityContainer container)
    {
        // NOTE: To load from web.config uncomment the line below.
        // Make sure to add a Unity.Configuration to the using statements.
        // container.LoadConfiguration();

        // TODO: Register your type's mappings here.
        // container.RegisterType<IProductRepository, ProductRepository>();

        container.RegisterFactory<ITestService>(c => PerRequestMiddleware.GetInstance(() => new TestService()));
    }
}

Теперь мой вопрос: содержит ли мой обходной путь какие-либо недостатки или слабости?


person Zenilogix    schedule 02.07.2019    source источник


Ответы (2)


Немного опоздал на вечеринку, но, потратив несколько часов на поиск хорошего решения, я нашел этот пост в блоге: https://codingsight.com/using-single-ioc-container-http-request-web-api-vs-owin-middleware/

Идея одинакова для WebApi и MVC, где ПО промежуточного слоя Owin создает контейнер с областью действия или дочерний контейнер и разрешение зависимостей в т.е. MVC выполняется с использованием этого контейнера с областью действия из OwinContext.

person Markus Knappen Johansson    schedule 16.11.2020

Предоставленный вами код решает проблему в проекте OwinPerRequestUnityMiddleware на Github здесь.

Однако одна вещь, которую я заметил при использовании этого кода, заключается в том, что объекты IDisposable удаляются до того, как пользовательские поставщики сериализаторов OData завершат создание ресурсов.

Если вам нужен объект даже после окончания срока службы конвейера OWIN, вы можете использовать класс Factory в контейнере.

Например, вместо DbContext используйте IDbContextFactory<MyDbContext>.

person Rami A.    schedule 26.09.2019