Scheduling Jobs with Quartz.NET in ASP.NET Core

By FoxLearn 1/3/2025 4:45:47 AM   97
Quartz.NET is a job scheduling framework that allows you to schedule background tasks in an ASP.NET Core application, often for tasks that need to run at predefined intervals.

It is a .NET port of the popular Java-based Quartz framework and offers robust support for Cron expressions.

Create an ASP.NET Core API project

To create an ASP.NET Core API project in Visual Studio, follow these steps:

  1. Open Visual Studio and click on "Create new project."
  2. In the "Create new project" window, select "ASP.NET Core Web Application" from the templates list, then click Next.
  3. Specify the project name and location in the next window and click Create.
  4. Choose .NET Core as the runtime and select ASP.NET Core 2.2 (or later), with ASP.NET Core 8.0 as the version.
  5. Select the "API" project template and ensure that "Enable Docker Support" and "Configure for HTTPS" are unchecked. Set Authentication to "No Authentication."
  6. Click Create to generate the project.
  7. In Solution Explorer, right-click on the Controllers folder and select "Add -> Controller" to create a new controller, such as DefaultController.

To use Quartz, install the Quartz package from NuGet. You can do this through the Visual Studio NuGet package manager or by running the following command in the NuGet package manager console:

Install-Package Quartz

Quartz.NET jobs, triggers, and schedulers

In Quartz.NET, the key concepts are jobs, triggers, and schedulers. A job contains the task to be executed and is represented by a class implementing the IJob interface. A trigger defines the schedule and execution details for a job. The scheduler manages polling and running jobs according to the predefined schedules.

How to Create a scheduler using Quartz.NET?

You can have multiple schedulers in an application, but for simplicity, we'll use one.

To create a scheduler instance, use the following code:

var scheduler = StdSchedulerFactory.GetDefaultScheduler().GetAwaiter().GetResult();

After creating the scheduler, add it as a singleton service in the ConfigureServices method of the Startup.cs file.

services.AddSingleton(scheduler);

How to Start and stop a scheduler using Quartz.NET?

To start and stop the scheduler, create a class that implements the IHostingService interface. This allows you to manage the scheduler's lifecycle within the application.

public class CustomQuartzHostedService : IHostedService
{
    private readonly IScheduler _scheduler;
    public CustomQuartzHostedService(IScheduler scheduler)
    {
        _scheduler = scheduler;
    }
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        await _scheduler?.Start(cancellationToken);
    }
    public async Task StopAsync(CancellationToken cancellationToken)
    {
        await _scheduler?.Shutdown(cancellationToken);
    }
}

You should register the hosted service in the services collection within the ConfigureServices method using the appropriate code snippet.

services.AddHostedService<QuartzHostedService>();

Here is the updated ConfigureServices method for reference:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    var scheduler =
    StdSchedulerFactory.GetDefaultScheduler().GetAwaiter().GetResult();
    services.AddSingleton(scheduler);
    services.AddHostedService<QuartzHostedService>();
}

How to Create a job using Quartz.NET

A job in Quartz.NET is a class that implements the IJob interface and contains an Execute() method, which takes an IJobExecutionContext instance. The following code example shows a job class with an asynchronous Execute() method, where the task to be performed is defined.

[DisallowConcurrentExecution]
public class NotificationJob : IJob
{
    private readonly ILogger<NotificationJob> _logger;
    public NotificationJob(ILogger<NotificationJob> logger)
    {
        _logger = logger;
    }
    public Task Execute(IJobExecutionContext context)
    {
        _logger.LogInformation("Hello world!");
        return Task.CompletedTask;
    }
}

How to Create a job factory using Quartz.NET

A job factory is a class that implements the IJobFactory interface, including the NewJob() and ReturnJob() methods. The following code snippet demonstrates how to create a factory class that generates and returns job instances.

public class CustomQuartzJobFactory : IJobFactory
{
    private readonly IServiceProvider _serviceProvider;
    public CustomQuartzJobFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    public IJob NewJob(TriggerFiredBundle triggerFiredBundle,
    IScheduler scheduler)
    {
        var jobDetail = triggerFiredBundle.JobDetail;
        return (IJob)_serviceProvider.GetService(jobDetail.JobType);
    }
    public void ReturnJob(IJob job) { }
}

This implementation doesn't utilize job pooling. To enable job pooling, you need to modify the NewJob() method and implement the ReturnJob() method.

How to Create a JobMetadata class to store your job metadata

A custom class will be used to store job metadata, such as job ID, name, and other details. The following class represents this job metadata.

public class JobMetadata
{
    public Guid JobId { get; set; }
    public Type JobType { get; }
    public string JobName { get; }
    public string CronExpression { get; }
    public JobMetadata(Guid Id, Type jobType, string jobName,
    string cronExpression)
    {
        JobId = Id;
        JobType = jobType;
        JobName = jobName;
        CronExpression = cronExpression;
    }
}

How to Create a hosted service to start and stop the Quartz.NET scheduler

A hosted service is a class that implements the IHostedService interface and starts the Quartz scheduler. The following code example shows how to create a custom hosted service class.

public class CustomQuartzHostedService : IHostedService
{
    private readonly ISchedulerFactory schedulerFactory;
    private readonly IJobFactory jobFactory;
    private readonly JobMetadata jobMetadata;
    public CustomQuartzHostedService(ISchedulerFactory
        schedulerFactory,
        JobMetadata jobMetadata,
        IJobFactory jobFactory)
    {
        this.schedulerFactory = schedulerFactory;
        this.jobMetadata = jobMetadata;
        this.jobFactory = jobFactory;
    }
    public IScheduler Scheduler { get; set; }
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        Scheduler = await schedulerFactory.GetScheduler();
        Scheduler.JobFactory = jobFactory;
        var job = CreateJob(jobMetadata);
        var trigger = CreateTrigger(jobMetadata);
        await Scheduler.ScheduleJob(job, trigger, cancellationToken);
        await Scheduler.Start(cancellationToken);
    }
    public async Task StopAsync(CancellationToken cancellationToken)
    {
        await Scheduler?.Shutdown(cancellationToken);
    }
    private ITrigger CreateTrigger(JobMetadata jobMetadata)
    {
        return TriggerBuilder.Create()
        .WithIdentity(jobMetadata.JobId.ToString())
        .WithCronSchedule(jobMetadata.CronExpression)
        .WithDescription($"{jobMetadata.JobName}")
        .Build();
    }
    private IJobDetail CreateJob(JobMetadata jobMetadata)
    {
        return JobBuilder
        .Create(jobMetadata.JobType)
        .WithIdentity(jobMetadata.JobId.ToString())
        .WithDescription($"{jobMetadata.JobName}")
        .Build();
    }
}

The following code shows the complete ConfigureServices method in the Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddSingleton<IJobFactory, CustomQuartzJobFactory>();
    services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
    services.AddSingleton<NotificationJob>();
    services.AddSingleton(new JobMetadata(Guid.NewGuid(), typeof(NotificationJob), "Notification Job", "0/10 * * * * ?"));
    services.AddHostedService<CustomQuartzHostedService>();
}

Once this is done, running the application will trigger the Execute() method of the NotificationJob class every 10 seconds.

Quartz.NET is an excellent choice for implementing schedulers in applications, and it also offers a persistence feature to store jobs in databases like SQL Server, PostgreSQL, or SQLite.