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;
}
}