frodo_ff
frodo_ff

Reputation: 31

Full Dependency Access using Background Service

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:

nothing sucesfully injected

Upvotes: 0

Views: 359

Answers (1)

Ivan
Ivan

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

Related Questions