PerRequestLifetimeManager 只能在 HTTP 请求的上下文中使用
PerRequestLifetimeManager can only be used in the context of an HTTP request
我有一个使用 Unity 作为其 IoC 容器的 MVC 应用程序,并且在我的应用程序中使用 PerRequestLifetimeManager
.
定义了多个服务
container.RegisterType<IFileService, FileService>();
一切正常,除了当我尝试滚动我的解决方案以自动执行任务(如 SharePoint TimerJobs)时,在不同的时间间隔开始。
为此,我在一个单独的项目中定义了一个 ServiceLocator
-Type class 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;
}
我错过了什么?
您正在使用 is considered an anti-pattern.
的服务位置
话虽如此,这里是对您问题的直接回答:
解决问题的一种方法是使用 named registrations:
假设您正在使用 PerRequestLifetimeManager
生命周期管理器将 IService
注册到 Service
:
container.RegisterType<IService, Service>(new PerRequestLifetimeManager());
您还可以为相同类型添加另一个注册,但使用不同的生命周期管理器。但是,为了区分这个和以前的注册,你必须给它起一个这样的名字:
container.RegisterType<IService, Service>("transient_service", new TransientLifetimeManager());
这里我用 Service
注册 IService
并使用瞬态生命周期管理器。我给这个注册的名字是 "transient_service"
,但你可以在这里使用任何名字。
现在,从后台线程中,您可以像这样找到此服务:
var service = container.Resolve<IService>("transient_service");
我在这里假设您可以访问容器(您正在通过服务定位器执行此操作)。您可能需要更新服务定位器以使其能够按名称定位服务。
更新:
这是另一个解决方案:
如果当前线程中有 HttpContext,您可以创建一个自定义生命周期管理器作为 PerRequestLifetimeManager
生命周期管理器,如果没有,它将回退到 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 个单独的容器,具有不同的配置,用于 Web 环境和后台环境。因此,对于您的 Web 环境,您可以控制每个请求的生命周期,并且在后台任务中您可以按线程执行。
当您使用服务定位器时,您可以有 2 个定位器,例如 WebServiceLocator.Resolve<> 和 BackgroundServiceLocator.Resolve<>
我有一个使用 Unity 作为其 IoC 容器的 MVC 应用程序,并且在我的应用程序中使用 PerRequestLifetimeManager
.
container.RegisterType<IFileService, FileService>();
一切正常,除了当我尝试滚动我的解决方案以自动执行任务(如 SharePoint TimerJobs)时,在不同的时间间隔开始。
为此,我在一个单独的项目中定义了一个 ServiceLocator
-Type class 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;
}
我错过了什么?
您正在使用 is considered an anti-pattern.
的服务位置话虽如此,这里是对您问题的直接回答:
解决问题的一种方法是使用 named registrations:
假设您正在使用 PerRequestLifetimeManager
生命周期管理器将 IService
注册到 Service
:
container.RegisterType<IService, Service>(new PerRequestLifetimeManager());
您还可以为相同类型添加另一个注册,但使用不同的生命周期管理器。但是,为了区分这个和以前的注册,你必须给它起一个这样的名字:
container.RegisterType<IService, Service>("transient_service", new TransientLifetimeManager());
这里我用 Service
注册 IService
并使用瞬态生命周期管理器。我给这个注册的名字是 "transient_service"
,但你可以在这里使用任何名字。
现在,从后台线程中,您可以像这样找到此服务:
var service = container.Resolve<IService>("transient_service");
我在这里假设您可以访问容器(您正在通过服务定位器执行此操作)。您可能需要更新服务定位器以使其能够按名称定位服务。
更新:
这是另一个解决方案:
如果当前线程中有 HttpContext,您可以创建一个自定义生命周期管理器作为 PerRequestLifetimeManager
生命周期管理器,如果没有,它将回退到 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 个单独的容器,具有不同的配置,用于 Web 环境和后台环境。因此,对于您的 Web 环境,您可以控制每个请求的生命周期,并且在后台任务中您可以按线程执行。
当您使用服务定位器时,您可以有 2 个定位器,例如 WebServiceLocator.Resolve<> 和 BackgroundServiceLocator.Resolve<>