PerRequestLifetimeManager может использоваться только в контексте HTTP-запроса

У меня есть приложение MVC, которое использует Unity в качестве контейнера IoC, и в моем приложении определены несколько служб, использующих PerRequestLifetimeManager,

container.RegisterType<IFileService, FileService>();

Все работает отлично, за исключением случаев, когда я пытался развернуть свое решение для автоматизации задач (например, SharePoint TimerJobs), запущенных через различные промежутки времени.

Для этого я определил ServiceLocatorТип класса ContainerManager в отдельном проекте, который по сути делает это:

    public static object Resolve(string typeName)
    {
        var type = Type.GetType(typeName);
        return Resolve(type);
    }

    public static object Resolve(Type type)
    {
        object result = DependencyResolver.Current.GetService(type);
        return result;
    }

    public static T Resolve<T>() where T : class
    {
        object result = DependencyResolver.Current.GetService<T>();
        return (T)result;
    }

    public static object Resolve(string typeName)
    {
        var type = Type.GetType(typeName);
        return Resolve(type);
    }

    public static object Resolve(Type type)
    {
        object result = DependencyResolver.Current.GetService(type);
        return result;
    }

    public static T Resolve<T>() where T : class
    {
        object result = DependencyResolver.Current.GetService<T>();
        return (T)result;
    }

И внутри моего "TaskManager" я делаю следующее:

var unitOfWork = ContainerManager.Resolve<IFileService>();

Теперь это работает, когда запускается вручную (при отправке из HttpRequest). Тем не менее, это не работает при запуске через мой фоновый поток.

Я попытался вызвать Unity напрямую (без моего ServiceLocator), но потом я получу исключение: PerRequestLifetimeManager can only be used in the context of an HTTP request

Вот как я создаю свои задачи:

    private ITask CreateTask()
    {
        ITask task = null;
        if (IsEnabled)
        {
            var type = System.Type.GetType(Type);
            if (type != null)
            {
                object instance = ContainerManager.Resolve(type);
                if (instance == null)
                {
                    // Not resolved
                    instance = ContainerManager.ResolveUnregistered(type);
                }

                task = instance as ITask;
            }
        }

        return task;
    }

Что мне не хватает?

3 ответа

Решение

Вы используете Serivice Location, который считается анти-паттерном.

Сказав это, вот прямой ответ на ваш вопрос:

Один из способов решить вашу проблему - использовать именованные регистрации:

Скажем что вы регистрируетесь IService в Service с использованием PerRequestLifetimeManager пожизненный менеджер вот так:

container.RegisterType<IService, Service>(new PerRequestLifetimeManager());

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

container.RegisterType<IService, Service>("transient_service", new TransientLifetimeManager());

Здесь я регистрируюсь IService с Service и используя временный менеджер продолжительности жизни. Имя, которое я даю этой регистрации: "transient_service", но вы можете использовать любое имя здесь.

Теперь из фоновой ветки вы можете найти этот сервис следующим образом:

var service = container.Resolve<IService>("transient_service");

Я предполагаю, что у вас есть доступ к контейнеру (что вы делаете через локатор службы). Вам может потребоваться обновить ваш локатор сервисов, чтобы он мог найти сервисы по имени.

ОБНОВИТЬ:

Вот еще одно решение:

Вы можете создать собственный менеджер времени жизни, который будет действовать как PerRequestLifetimeManager Менеджер времени жизни, если в текущем потоке есть HttpContext, и это откатится к TransientLifetimeManager если нет

Вот как будет выглядеть такой пожизненный менеджер:

public class PerRequestOrTransientLifeTimeManager : LifetimeManager
{
    private readonly PerRequestLifetimeManager m_PerRequestLifetimeManager = new PerRequestLifetimeManager();
    private readonly TransientLifetimeManager m_TransientLifetimeManager = new TransientLifetimeManager();

    private LifetimeManager GetAppropriateLifetimeManager()
    {
        if (System.Web.HttpContext.Current == null)
            return m_TransientLifetimeManager;

        return m_PerRequestLifetimeManager;
    }

    public override object GetValue()
    {
        return GetAppropriateLifetimeManager().GetValue();
    }

    public override void SetValue(object newValue)
    {
        GetAppropriateLifetimeManager().SetValue(newValue);
    }

    public override void RemoveValue()
    {
        GetAppropriateLifetimeManager().RemoveValue();
    }
}

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

ОБНОВЛЕНИЕ 2:

Пользовательский код LifetimeManger не будет работать с Unity 3.0 или более поздней версией, поскольку он был полностью переписан и дополнительно абстрагирован в новые пакеты Nuget. Вот обновленный код:

public class PerRequestOrTransientLifeTimeManager : LifetimeManager
{
    private readonly PerRequestLifetimeManager _perRequestLifetimeManager = new PerRequestLifetimeManager();
    private readonly TransientLifetimeManager _transientLifetimeManager = new TransientLifetimeManager();

    private LifetimeManager GetAppropriateLifetimeManager()
    {
        if (HttpContext.Current == null)
        {
            return _transientLifetimeManager;
        }

        return _perRequestLifetimeManager;
    }

    public override object GetValue(ILifetimeContainer container = null)
    {
        return GetAppropriateLifetimeManager().GetValue();
    }

    public override void SetValue(object newValue, ILifetimeContainer container = null)
    {
        GetAppropriateLifetimeManager().SetValue(newValue);
    }

    public override void RemoveValue(ILifetimeContainer container = null)
    {
        GetAppropriateLifetimeManager().RemoveValue();
    }

    protected override LifetimeManager OnCreateLifetimeManager()
    {
        return this;
    }
}

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

Поскольку вы используете локатор службы, у вас может быть 2 локатора, таких как WebServiceLocator.Resolve<> и BackgroundServiceLocator.Resolve<>

Обновление 2023 года к принятому ответу

Теперь код должен выглядеть так:

      public class PerRequestOrHierarchicalLifeTimeManager : LifetimeManager, ITypeLifetimeManager,
    IFactoryLifetimeManager
{
    private readonly PerRequestLifetimeManager perRequestLifetimeManager = new PerRequestLifetimeManager();
    private readonly HierarchicalLifetimeManager hierarchicalLifetimeManager = new HierarchicalLifetimeManager();

    private LifetimeManager GetAppropriateLifetimeManager()
    {
        if (HttpContext.Current == null)
        {
            return hierarchicalLifetimeManager;
        }

        return perRequestLifetimeManager;
    }

    public override object GetValue(ILifetimeContainer container = null)
    {
        return GetAppropriateLifetimeManager().GetValue(container);
    }

    public override void SetValue(object newValue, ILifetimeContainer container = null)
    {
        GetAppropriateLifetimeManager().SetValue(newValue, container);
    }

    public override void RemoveValue(ILifetimeContainer container = null)
    {
        GetAppropriateLifetimeManager().RemoveValue(container);
    }

    protected override LifetimeManager OnCreateLifetimeManager()
    {
        return this;
    }

    public LifetimeManager CreateLifetimePolicy()
    {
        return this;
    }
}
Другие вопросы по тегам