Jobs
In a modern web application, not all work happens in direct response to a user’s click. Many essential tasks need to run independently in the background. These “background jobs” are the unsung heroes of your application, tirelessly working behind the scenes to handle tasks like:
- Sending email newsletters
- Processing large data imports
- Generating nightly reports
- Cleaning up old records
- Synchronizing data with external systems
SliceFlow provides a clean, powerful, and integrated way to create, manage, and run these background jobs, building on top of the solid foundation of .NET’s built-in BackgroundService
.
The Anatomy of a Background Job
Think of a background job as a long-running service that starts up with your application and waits patiently for work to do. In SliceFlow, these jobs are typically designed to listen to the messaging system. They subscribe to a specific queue (a “mailbox”) and spring into action whenever a new message arrives.
This architecture creates a powerful and scalable system. Your main web application can quickly fire off a message to a queue—a very fast operation—and a dedicated background job will pick it up and handle the actual time-consuming work, ensuring your users never have to wait.
SliceFlow’s Built-in Workers
Out of the box, SliceFlow comes with pre-built background jobs for handling its asynchronous email system. There are two dedicated jobs:
SendSimpleEmails
: This job listens for and processes basic email messages.SendTemplateEmails
: This job handles the more complex task of rendering Razor templates and sending beautiful, dynamic emails.
These jobs serve as a perfect blueprint for creating your own custom background workers.
Creating Your Own Background Jobs
Building a custom background job in SliceFlow is straightforward. You simply create a new class that inherits from BackgroundService
and implement your logic.
A common pattern looks like this:
- Start-Up: In the
ExecuteAsync
method, your job connects to the messaging service and subscribes to the queue it’s interested in. It then waits indefinitely for messages to arrive. - Processing: You create a handler method that will be executed for each message. This is where you put your core business logic—the actual work the job needs to do.
- Shut-Down: You implement the
StopAsync
method to gracefully disconnect from the message queue when the application is shutting down, ensuring no work is lost.
Here’s a simplified example of a job that processes video files:
public class VideoProcessingJob : BackgroundService{ // ... constructor and dependencies ...
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // 1. Subscribe to the video processing queue when the job starts await _messageService.SubscribeAsync<VideoMessage>( QueueEnum.VideoProcessing, ProcessVideoAsync, // The method to call for each message stoppingToken); }
private async Task ProcessVideoAsync(VideoMessage message) { // 2. Do the heavy lifting here _logger.LogInformation("Starting to process video {VideoId}", message.VideoId); // ... complex video processing logic ... _logger.LogInformation("Finished processing video {VideoId}", message.VideoId); }
public override async Task StopAsync(CancellationToken cancellationToken) { // 3. Clean up when the application shuts down await _messageService.UnsubscribeAsync(QueueEnum.VideoProcessing, cancellationToken); await base.StopAsync(cancellationToken); }}
Handling Dependencies and Scoped Services
A crucial aspect of background jobs is managing dependencies correctly. A background job itself lives for the entire lifetime of the application (it’s a “singleton”). However, the services it uses, like a database context (DataContext
), should be short-lived (or “scoped”) to a single operation to prevent issues.
SliceFlow’s pattern handles this beautifully. Inside your message processing handler, you create a new “scope” for each message. This ensures that every task gets a fresh set of services, including its own database connection, preventing conflicts and ensuring your job is robust and reliable.
private async Task ProcessVideoAsync(VideoMessage message){ // Create a new scope for this specific task using var scope = _serviceProvider.CreateScope();
// Get a fresh, scoped DbContext just for this operation var dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();
// Use the DbContext to work with the database var video = await dbContext.Videos.FindAsync(message.VideoId); // ... update video status ... await dbContext.SaveChangesAsync();}
By providing a clear structure, seamless integration with the messaging system, and a robust pattern for dependency management, SliceFlow makes it easy to add powerful, scalable background processing to your applications.