Introduction to Quartz Scheduler
Quartz is a feature-rich, open-source job scheduling library that can be integrated into virtually any application. For .NET Core developers, Quartz.NET provides a powerful solution for implementing background tasks that need to run on specific schedules. Using Cron expressions, Quartz allows for complex scheduling rules that can accommodate almost any timing requiremant.
Setting Up the Project
To begin implementing scheduled tasks with Quartz in your .NET Core application, you first need to add the necessary NuGet package. Search for and install the Quartz package in your project.
Creating Task Classes
Each scheduled task must implement the IJob interface. Let's create two different task classes that will run on different schedules:
[DisallowConcurrentExecution]
public class NotificationJob : IJob
{
private readonly ILogger<NotificationJob> _logger;
public NotificationJob(ILogger<NotificationJob> logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Sending notifications to users");
return Task.CompletedTask;
}
}
[DisallowConcurrentExecution]
public class DataProcessingJob : IJob
{
private readonly ILogger<DataProcessingJob> _logger;
public DataProcessingJob(ILogger<DataProcessingJob> logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Processing batch data");
return Task.CompletedTask;
}
}
The DisallowConcurrentExecution attribute ensures that only one instance of each job runs at any given time, preventing overlapping executions.
Implementing a Custom Job Factory
To properly integrate with .NET Core's dependency injection system, we need to create a custom job factory:
public class DependencyInjectionJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
public DependencyInjectionJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
}
public void ReturnJob(IJob job)
{
// No action needed as DI manages the job lifecycle
}
}
Defining Job Schedules
Let's create a class to represent our job schedules:
public class TaskSchedule
{
public TaskSchedule(Type jobType, string cronExpression)
{
JobType = jobType;
CronExpression = cronExpression;
}
public Type JobType { get; }
public string CronExpression { get; }
}
Configuring Services in Startup
In your Startup.cs file, configure the necessary services in the ConfigureServices method:
// Add Quartz services
services.AddSingleton<IJobFactory, DependencyInjectionJobFactory>();
services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
// Register job classes
services.AddSingleton<NotificationJob>();
services.AddSingleton<DataProcessingJob>();
// Configure job schedules
ConfigureTaskSchedules(services);
// Add the Quartz hosted service
services.AddHostedService<QuartzBackgroundService>();
Add the configuration method for task schedules:
public static void ConfigureTaskSchedules(IServiceCollection services)
{
services.AddSingleton(new TaskSchedule(
jobType: typeof(NotificationJob),
cronExpression: "0/15 * * * * ?")); // Run every 15 seconds
services.AddSingleton(new TaskSchedule(
jobType: typeof(DataProcessingJob),
cronExpression: "0/30 * * * * ?")); // Run every 30 seconds
}
Creating the Background Service
Finally, implement the background service that manages the Quartz scheduler:
public class QuartzBackgroundService : IHostedService
{
private readonly ISchedulerFactory _schedulerFactory;
private readonly IJobFactory _jobFactory;
private readonly IEnumerable<TaskSchedule> _taskSchedules;
private IScheduler _scheduler;
public QuartzBackgroundService(
ISchedulerFactory schedulerFactory,
IJobFactory jobFactory,
IEnumerable<TaskSchedule> taskSchedules)
{
_schedulerFactory = schedulerFactory;
_jobFactory = jobFactory;
_taskSchedules = taskSchedules;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
_scheduler.JobFactory = _jobFactory;
foreach (var schedule in _taskSchedules)
{
var jobDetail = BuildJobDetail(schedule);
var trigger = BuildTrigger(schedule);
await _scheduler.ScheduleJob(jobDetail, trigger, cancellationToken);
}
await _scheduler.Start(cancellationToken);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await _scheduler?.Shutdown(cancellationToken);
}
private IJobDetail BuildJobDetail(TaskSchedule schedule)
{
return JobBuilder
.Create(schedule.JobType)
.WithIdentity(schedule.JobType.FullName)
.WithDescription($"Automated task: {schedule.JobType.Name}")
.Build();
}
private ITrigger BuildTrigger(TaskSchedule schedule)
{
return TriggerBuilder
.Create()
.WithIdentity($"{schedule.JobType.FullName}-trigger")
.WithCronSchedule(schedule.CronExpression)
.WithDescription($"Cron schedule: {schedule.CronExpression}")
.Build();
}
}
Running the Application
When you run your application, you'll see the scheduled tasks executing according to their defined cron schedules. The NotificationJob will run every 15 seconds, while the DataProcessingJob will run every 30 seconds, with all output appearing in your application logs.