Reputation: 1049
I have a task that needs to be run in a separate thread in the background, and I am using SignalR to report progress. This worked some time ago, and I had made some code modifications, but I am at a complete loss as to the error I receive now:
"No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself."
Any help is greatly appreciated!
public ActionResult DoAction(IEnumerable<string> items){
//...
Func<CancellationToken, Task> taskFunc = CancellationToken => performAction(items);
HostingEnvironment.QueueBackgroundWorkItem(taskFunc);
//...
}
private async Task performAction(IEnumerable<string> items){
var svc = AutofacDependencyResolver.Current.AppicationContainer.BeginLifetimeScope().Resolve<MyService>();
svc.Method(items);
}
public class MyService{
private EntityContext db;
public MyService(EntityContext db){
this.db = db;
}
}
In my Startup.Container.cs file:
builder.RegisterType<MyService>().As<MyService>().InstancePerLifetimeScope();
builder.RegisterType<EntityContext>().InstancePerRequest();
Upvotes: 1
Views: 4818
Reputation: 13767
I did something similar to @Chima Osuji but I think something is off in his answer so I'm gonna describe my solution and explain it.
public class BackgroundTaskFactory : IBackgroundTaskFactory
{
private ILifetimeScope lifetimeScope;
public BackgroundTaskFactory(ILifetimeScope lifetimeScope)
{
this.lifetimeScope = lifetimeScope;
}
public Task Run<T>(Action<T> action)
{
Task task = Task.Factory.StartNew(() =>
{
using (var lifetimeScope = this.lifetimeScope.BeginLifetimeScope())
{
var service = lifetimeScope.Resolve<T>();
action(service);
}
});
return task;
}
}
It's important to point out that my Run method is returning the task that was created on Task.Factory.StartNew. That way someone waits for the result, he gets the right task. In the other solutions they are returning Task.FromResult(0) which returns a dummy task.
BeginLifetimeScope creates a new scope as a child of the injected scope. If the injected scope is an InstancePerLifetimeScope associated to a web request, as soon as the web request scope is disposed, this new scope will also be disposed and it will error out. Child scopes cannot live longer than its parent scopes. Solution? Register BackgroundTaskFactory as singleton. When you do that, the injected lifetime scope will be the root scope, which doesn't get disposed until the app is disposed.
containerBuilder.RegisterType< BackgroundTaskFactory >() .As< IBackgroundTaskFactory >() .SingleInstance();
Upvotes: 1
Reputation: 8098
An updated answer based on the code above:
Usage:
public class ServiceModule :Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<AutoFac.AsyncRunner>().As<AutoFac.IAsyncRunner>().SingleInstance();
}
}
public class Controller
{
private AutoFac.IAsyncRunner _asyncRunner;
public Controller(AutoFac.IAsyncRunner asyncRunner)
{
_asyncRunner = asyncRunner;
}
public void Function()
{
_asyncRunner.Run<IService>((cis) =>
{
try
{
//do stuff
}
catch
{
// catch stuff
throw;
}
});
}
}
The Interface:
public interface IAsyncRunner
{
Task Run<T>(Action<T> action);
}
The class:
public class AsyncRunner : IAsyncRunner
{
private ILifetimeScope _lifetimeScope { get; set; }
public AsyncRunner(ILifetimeScope lifetimeScope)
{
//Guard.NotNull(() => lifetimeScope, lifetimeScope);
_lifetimeScope = lifetimeScope;
}
public Task Run<T>(Action<T> action)
{
Task.Factory.StartNew(() =>
{
using (var lifetimeScope = _lifetimeScope.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
{
var service = lifetimeScope.Resolve<T>();
action(service);
}
});
return Task.FromResult(0);
}
}
Upvotes: 0
Reputation: 391
I recently implemented something similar using help from this answer and this answer. You need to create a new lifetime scope - it sounds like your doing this in a web application, so you need to create the scope via the per-request tag (example below).
Another (non-StackOverflow) answer provides similar advice.
public Task Run<T>(Action<T> action)
{
Task.Factory.StartNew(() =>
{
using (var lifetimeScope = _container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
{
var service = lifetimeScope.Resolve<T>();
action(service);
}
});
return Task.FromResult(0);
}
Upvotes: 5