Reputation: 31
Given an ASP.NET Core API (dotnet8), using MediatR, i have a controller and want to archive the following:
[HttpPost]
public async Task<IActionResult> CreateAndPublish([FromBody] CreateAndPublishRequest request)
{
// create configuration (like metadata for a sth. and store to db)
// do not wait for this, run it in a background task
await Mediator.Send(new CollectAndZipDataCommand());
await Mediator.Send(new SendDownloadLinkPerMailCommand());
return Ok();
}
So the two Mediator commands should be executed, but take longer, so they can continue in the background and the controller will immediately return Ok().
Look at https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-8.0&tabs=visual-studio and implement it like this
_taskQueue.QueueBackgroundWorkItemAsync(async (cancellationToken, serviceProvider) => {
var mediator = serviceProvider.GetRequiredService<Mediator>();
await mediator.Send(new CollectAndZipDataCommand());
await mediator.Send(new SendDownloadLinkPerMailCommand());
});
allows me to successfully and correctly call Mediator, but the services of the CommandHandlers are null. I am quite sure it is the scope, but whatever I try, I cannot get a clean solution that resolves all the dependencies of my DI container in my background tasks.
Mediator successfully resolved but unfortunately, when CommandHandler ist called:
Upvotes: 0
Views: 359
Reputation: 98
The problem might cause in scope, I suggest you try code like this.
public class PublishController : Controller
{
private readonly BackgroundTaskQueue _backgroundTaskQueue;
private readonly IServiceScopeFactory _serviceScopeFactory;
public PublishController(BackgroundTaskQueue backgroundTaskQueue, IServiceScopeFactory serviceScopeFactory)
{
_backgroundTaskQueue = backgroundTaskQueue ?? throw new ArgumentNullException(nameof(backgroundTaskQueue));
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
}
[HttpPost]
public IActionResult CreateAndPublish([FromBody] CreateAndPublishRequest request)
{
_backgroundTaskQueue.QueueBackgroundWorkItem(async token =>
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Send(new CollectAndZipDataCommand(), token);
await mediator.Send(new SendDownloadLinkPerMailCommand(), token);
}
});
return Ok();
}
}
It works on me, you might need to define the queue.
For example:
public class BackgroundTaskQueue
{
private readonly ConcurrentQueue<Func<CancellationToken, Task>> _workItems = new ConcurrentQueue<Func<CancellationToken, Task>>();
private readonly SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
Upvotes: 2