Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PerRequestLifetimeManager can only be used in the context of an HTTP request

I have a MVC application that uses Unity as its IoC container and have multiple services defined in my application using the PerRequestLifetimeManager.

container.RegisterType<IFileService, FileService>();

Everything works fine, except when I tried to roll my solution to automate tasks (like SharePoint TimerJobs), started in various intervals.

For that, I've defined a ServiceLocator-Type class ContainerManager in a separate project, that does essentially this:

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

And inside my "TaskManager" I do the following:

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

Now this works when started manually (when originating from an HttpRequest). However, this does not work when started via my background thread.

I've tried calling unity directly (without my ServiceLocator), but then I'll get the exception: PerRequestLifetimeManager can only be used in the context of an HTTP request

That's how I create my tasks:

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

What am I missing?

like image 349
SeToY Avatar asked Oct 17 '15 22:10

SeToY


2 Answers

You are using Serivice Location which is considered an anti-pattern.

Having said that, here is a direct answer to your question:

One way to solve your problem is using named registrations:

Let say that you are registering IService to Service using the PerRequestLifetimeManager lifetime manager like this:

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

You can also add another registration for the same types but with a different lifetime manager. However, to distinguished between this and the previous registration, you have to give it a name like this:

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

Here I am registering IService with Service and using the transient lifetime manager. The name I am giving to this registration is "transient_service" , but you can use any name here.

Now, from your background thread, you can locate this service like this:

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

I am assuming here that you have access to the container (which you are doing through the service locator). You might need to update your service locator to enable it to locate services by name.

UPDATE:

Here is another solution:

You can create a custom lifetime manager that acts as the PerRequestLifetimeManager lifetime manager if there is an HttpContext in the current thread, and that will fallback to a TransientLifetimeManager if there isn't.

Here is how such lifetime manager would look like:

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

You need to modify your registrations to use such lifetime manager.

UPDATE 2:

The custom LifetimeManger code won't work with Unity 3.0 or later since it was completely rewritten and further abstracted into new Nuget packages as well. Here is an updated code:

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;
    }
}
like image 88
Yacoub Massad Avatar answered Sep 17 '22 22:09

Yacoub Massad


I would suggest you to have 2 separate containers, with different configuration, for the web environment and for the background environment. So, for your web environment, you can control the lifetime per request and in a background task you can do it per thread.

As you are using service locator, you could have 2 locators, like WebServiceLocator.Resolve<> and BackgroundServiceLocator.Resolve<>

like image 39
Francesc Castells Avatar answered Sep 20 '22 22:09

Francesc Castells