PerRequestLifetimeManager只能在HTTP请求的上下文中使用

问题描述 投票:5回答:2

我有一个MVC应用程序,它使用Unity作为其IoC容器,并使用PerRequestLifetimeManager在我的应用程序中定义了多个服务。

container.RegisterType<IFileService, FileService>();

一切正常,除非我尝试将我的解决方案推广到自动化任务(如SharePoint TimerJobs),并以不同的时间间隔启动。

为此,我在一个单独的项目中定义了一个ServiceLocator-Type类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;
    }

我错过了什么?

c# unity-container asp.net-mvc-5.2
2个回答
12
投票

您正在使用qazxsw poi的服务地点。

话虽如此,这里是您问题的直接答案:

解决问题的一种方法是使用is considered an anti-pattern

假设您使用named registrations终身经理将IService注册到Service,如下所示:

PerRequestLifetimeManager

您还可以为相同类型添加另一个注册,但使用不同的生命周期管理器。但是,为了区分这个和之前的注册,你必须给它一个这样的名字:

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

在这里,我正在使用container.RegisterType<IService, Service>("transient_service", new TransientLifetimeManager()); 注册IService并使用瞬态生命周期管理器。我给这个注册的名字是Service,但你可以在这里使用任何名字。

现在,从您的后台线程,您可以找到这样的服务:

"transient_service"

我在这里假设您可以访问容器(您通过服务定位器执行此操作)。您可能需要更新服务定位器以使其能够按名称查找服务。

更新:

这是另一个解决方案:

如果当前线程中存在HttpContext,则可以创建充当var service = container.Resolve<IService>("transient_service"); 生命周期管理器的自定义生命周期管理器,如果没有,则将回退到PerRequestLifetimeManager

以下是这样的终身经理的样子:

TransientLifetimeManager

您需要修改注册才能使用此终身经理。

更新2:

自定义LifetimeManger代码不适用于Unity 3.0或更高版本,因为它已完全重写并进一步抽象为新的Nuget包。这是一个更新的代码:

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

3
投票

我建议您为Web环境和背景环境提供2个不同配置的独立容器。因此,对于您的Web环境,您可以控制每个请求的生命周期,并且在后台任务中,您可以按线程执行此操作。

当您使用服务定位器时,您可以拥有2个定位器,例如WebServiceLocator.Resolve <>和BackgroundServiceLocator.Resolve <>

© www.soinside.com 2019 - 2024. All rights reserved.