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