Reputation: 5208
I am new to Autofac
, I really like its lifetime scope features, but I am having an issue.
I need to use an instance of an object created on a InstancePerRequest
basis from a singleton in an assembly that doesn't have a refence to .Net MVC Web
I tried what this post suggests but it doesn't work. I get this exception:
No scope with a tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested.
What is the correct way to get/use an InstancePerRequest object in a Singleton?
Update I would like to clarify, considering Travis answer, we don't want to hold a captive dependency of the InstancePerRequest item in the singleton. We want to request the item each time we use it. We've analyzed and it would be more expensive to make the singleton an InstancePerRequest than request the InstancePerRequest item to the container each time we use it. Is this considered a bad practice? It is rather common to have certain things you need to access at the Request level and have a Singleton use them.
Upvotes: 1
Views: 1378
Reputation: 5208
Thanks everyone for your answers and comments, It turns out the solution was rather simple. This is the structure of files I wind up having:
No-Web assembly
public IPerRequestDependencyResolver
{
T Get<T>() where T : class;
}
public interface IPerRequestObject
{
void DoSomething();
}
public class PerRequestObject: IPerRequestObject
{
public void DoSomething()
{}
}
public interface IMySingletonPerRequestWrapper
{
DoSomethingOnPerRequestBasis();
}
public class MySingletonPerRequestWrapper: IMySingletonPerRequestWrapper
{
private IPerRequestDependencyResolver perRequestDependencyResolver;
public PerRequestWrapper(IPerRequestDependencyResolver perRequestDependencyResolver)
{
this.perRequestDependencyResolver = perRequestDependencyResolver;
}
public void DoSomethingOnPerRequestBasis()
{
this.perRequestDependencyResolver.Get<IPerRequestObject>().DoSomething();
}
}
public interface IMySingletonBussinesObject
{
void DoSomething();
}
public class MySingletonBussinessObject: IMySingletonBussinesObject
{
private IMySingletonPerRequestWrapper mysingletonPerRequestWrapper;
public MySingletonBussinessObject(IMySingletonPerRequestWrapper mysingletonPerRequestWrapper)
{
this.mysingletonPerRequestWrapper = mysingletonPerRequestWrapper;
}
public void DoSomething()
{
this.mysingletonPerRequestWrapper.DoSomethingOnPerRequestBasis();
}
}
MVC Web assembly
public class PerRequestDependencyResolver : IPerRequestDependencyResolver
{
public T Get<T>() where T : class
{
return DependencyResolver.Current.GetService<T>();
}
}
MySingletonPerRequestWrapper
, MySingletonBussinessObject
, PerRequestDependencyResolver
are configured as .SingleInstance()
in Autofac while PerRequestObject
is configured with `InstancePerRequest().
I create the MySingletonPerRequestWrapper
instead of directly using the PerRequestDependencyResolver
inside the MySingletonBussinessObject
because I want to make sure that anyone using the PerRequestObject
does so properly and no captive instances are kept by mistake.
I rather use Autofac this way to keep all my instances than keeping my instance in the HttpRequest
and using it from there (with a wrapper), I feel this is more flexible.
Update Web Api
This is the update to use this technique with WebApi. I didn't myself do the full implementation, we discussed it on my group and a friend implemented it. I will explain the approach he took.
I simplified the logic and classes for clarity, you can change whatever you need. In our system we configured Autofac so you can inject instances in static classes. You can change that to something that better suits you.
First some helper, Extension methods to simplify.
public static class Extensions
{
private const string dependencyScopeKey = "Resolver.Unique.key";
public static IExecutionContext ExecutionContext { get; set; }
public static IDependencyScope Current(this IDependencyResolver dependencyResolver)
{
return ExecutionContext.GetObject<IDependencyScope>(dependencyScopeKey);
}
public static void SetCurrentDependencyScope(this HttpRequestMessage request)
{
ExecutionContext.SetObject(dependencyScopeKey, request.GetDependencyScope());
}
}
public class WebExecutionContext : IExecutionContext
{
public T GetObject <T>(string key)
{
var result = HttpContext.Current.Items[key];
return result != null ? (T) result : default(T);
}
public void SetObject(string key, object val)
{
if (HttpContext.Current.Items.Contains(key))
HttpContext.Current.Items.Remove(key);
HttpContext.Current.Items.Add(key, val);
}
}
Then the WebApi implementation of the IPerRequestDependencyResolver, we make use of the GlobalConfiguration.
public class PerRequestWebAPIDependencyResolver : IPerRequestDependencyResolver
{
public T Get <T>() where T : class
{
var config = GlobalConfiguration.Configuration;
return (T)config.DependencyResolver.Current().GetService(typeof(T));
}
}
Then an action filter to help us add the DependencyResolver we need:
public class IoCScopeAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
//ioc context set
filterContext.Request.SetCurrentDependencyScope();
}
}
Finally in your controller, or base controller [AuthorizationFilter] [IoCScope()] public class YourController : ApiController {
}
I hope this helps!
Upvotes: 0
Reputation: 377
Given the update from Dzyann
For me it sounds like you you need a Factory of the Instance per request instance. this way the Factory Func can be a singleton and the Captive Dependency is avoided. Code would look something like this.
public class MySingleton
{
private readonly Func<IMyInstancePerRequestType> _instanceFactory;
public MySingleton(Func<IMyInstancePerRequestType> instanceFactory)
{
_instanceFactory = instanceFactory;
}
public void DoStuff()
{
var myService = _instanceFactory();
myService.DoStuff();
}
}
Autofac will implicitly create the factory method for you. So extra registration should not be needed.
Make sure you take a look at http://docs.autofac.org/en/latest/resolve/relationships.html
Regarding the 'AutofacWebRequest' error you need to use the AutofacMvc or AutoFacWebAPI nuget packages and use the Owin extension methods during startup (given your using owin) registering this will create the 'AutofacWebRequest' lifetimscope.
in startup.cs it would be something like
app.UseAutofacMiddleware( iocContainer );
app.UseAutofacWebApi( configuration );
you refer to a post. in there we have
builder.Register<Func<IDataStore>>(c =>
{
var context = c.Resolve<IComponentContext>();
return context.Resolve<IDataStore>;
});
.InstancePerHttpRequest() could be added there.
Upvotes: 1
Reputation: 23924
Don't get per request items in a singleton. That creates a captive dependency which is bad news. Switch the singleton to be per request as well.
Upvotes: 3