You have the following options:
IHostedService
classes can be long running methods that run in the background for the lifetime of your app. In order to make them to handle some sort of background task, you need to implement some sort of “global” queue system in your app for the controllers to store the data/events. This queue system can be as simple as aSingleton
class with a ConcurrentQueue that you pass in to your controller, or something like anIDistributedCache
or more complex external pub/sub systems. Then you can just poll the queue in yourIHostedService
and run certain operations based on it. Here is a microsoft example ofIHostedService
implementation for handling queues https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#queued-background-tasks
Note that theSingleton
class approach can cause issues inmulti-server
environments.
Example implementation of the Singleton approach can be like:
// Needs to be registered as a Singleton in your Startup.cs
public class BackgroundJobs
{
public ConcurrentQueue<string> BackgroundTasks { get; set; } = new ConcurrentQueue<string>();
}
public class MyController : ControllerBase
{
private readonly BackgroundJobs _backgroundJobs;
public MyController(BackgroundJobs backgroundJobs)
{
_backgroundJobs = backgroundJobs;
}
public async Task<ActionResult> FireAndForgetEndPoint()
{
_backgroundJobs.BackgroundTasks.Enqueue("SomeJobIdentifier");
}
}
public class MyBackgroundService : IHostedService
{
private readonly BackgroundJobs _backgroundJobs;
public MyBackgroundService(BackgroundJobs backgroundJobs)
{
_backgroundJobs = backgroundJobs;
}
public void StartAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
if (_backgroundJobs.BackgroundTasks.TryDequeue(out var jobId))
{
// Code to do long running operation
}
Task.Delay(TimeSpan.FromSeconds(1)); // You really don't want an infinite loop here without having any sort of delays.
}
}
}
- Create a method that returns a
Task
, pass in aIServiceProvider
to that method and create a new Scope in there to make sure ASP.NET would not kill the task when the controller Action completes. Something like
IServiceProvider _serviceProvider;
public async Task<ActionResult> FireAndForgetEndPoint()
{
// Do stuff
_ = FireAndForgetOperation(_serviceProvider);
Return Ok();
}
public async Task FireAndForgetOperation(IServiceProvider serviceProvider)
{
using (var scope = _serviceProvider.CreateScope()){
await Task.Delay(1000);
//... Long running tasks
}
}
Update: Here is the Microsoft example of doing something similar: https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-3.1#do-not-capture-services-injected-into-the-controllers-on-background-threads