I've implemented the BackgroundQueue as explained here. This will replace the old HostingEnvironment. Furthermore, I'm refactoring the code to use Autofac to inject services into the background task.
Currently, the code is this:
public ActionResult SomeAction()
{
backgroundQueue.QueueBackgroundWorkItem(async ct =>
{
//Need to resolve services here...
}
return Ok();
}
backgroundQueue
is an instance of IBackgroundQueue
and is registered with Autofac as a singleton.
How can I pass the Autofac container to the task so I can register the services? Or is there a better way to register services in a task?
A solution might be doing this:
var myService = HttpContext.RequestServices.GetService(typeof(IMyService));
But this is considered as an anti-patten.
You will have to manage your own LifetimeScope
inside your task.
The easiest way would be to change the method QueueBackgroundWorkItem
to introduce a ILifetimeScope
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<ILifetimeScope, CancellationToken, Task> workItem);
Then
public ActionResult SomeAction()
{
backgroundQueue.QueueBackgroundWorkItem(async (scope, ct) =>
{
scope.Resolve<IService>().Do()
//Need to resolve services here...
}
return Ok();
}
You can get a new ILifetimeScope
using the BeginLifetimeScope
of an existing scope and ILifetimeScope
is a registered service.
If you use the QueueHostedService
implementation provided by the link you can change it as follow
public class QueueHostedService: IBackgroundTaskQueue {
public QueueHostedService(ILifetimeScope scope, ...) {
this._rootScope = scope;
}
private readonly ILifetimeScope _rootScope;
...
private async Task BackgroundProcessing(...) {
...
try {
using(ILifetimeScope queueScope = this._rootScope.BeginLifetimeScope()){
await workItem(queueScope, stoppingToken);
}
}
...
}
If you can't change the method definition, you can create the lifetime scope inside the task.
You can inject a ILifetimeScope
inside the controller but you can't create a LifetimeScope from it because it will be disposed at the end of the request. You can resolve a named lifetimescope which will be the root of all your queue lifetime scope
public class XController {
public XController(ILifetimeScope scope){
// you can also inject directly the named scope using named attribute or custom parameter, etc.
this._taskRootScope.ResolveNamed<ILifetimeScope>("taskRoot");
}
private readonly ILifetimeScope _taskRootScope;
public ActionResult SomeAction()
{
var taskRootScope = this._taskRootScope;
backgroundQueue.QueueBackgroundWorkItem(async ct =>
{
using(var taskScope = taskRootScope.BeginLifetimeScope()){
taskScope.Resolve<IService>().Do();
}
}
return Ok();
}
}
and registration will be something like
builder.Register(c => c.Resolve<ILifetimeScope>())
.Named<ILifetimeScope>("taskRoot")
.SingleInstance();
There is lot of other ways to handle the scope by yourself.
The next step could be to use method parameter injection like what ASP.net core does which will results to something like that :
backgroundQueue.QueueBackgroundWorkItem(async (IService service, CancellationToken ct) =>
{
//Need to resolve services here...
}
But it would requires lot of work
You may also consider using a dedicated framework like hangfire to make things easier.