Quartz.NET – Complete Guide with Code Samples
Quartz.NET is a powerful, open-source job scheduling library for .NET. It provides a robust framework for executing background tasks, ranging from simple recurring jobs to complex distributed workflows with clustering and persistence. This article compiles key concepts, capabilities, and code samples into one place.
Setup – .NET Generic Host + Quartz
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Spi;
var builder = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext,
services) =>
{
services.AddQuartz(q =>
{
q.SchedulerId =
"Scheduler-Core";
q.UseMicrosoftDependencyInjectionJobFactory();
q.AddJob<EmailReminderJob>(opts =>
opts.WithIdentity("EmailReminderJob").StoreDurably());
q.AddTrigger(opts => opts
.ForJob("EmailReminderJob")
.WithIdentity("EmailReminderTrigger")
.WithSimpleSchedule(x
=> x.WithIntervalInMinutes(15).RepeatForever())
);
});
services.AddQuartzHostedService(q
=> q.WaitForJobsToComplete = true);
services.AddScoped<IEmailSender, SmtpEmailSender>();
});
await builder.RunConsoleAsync();
Basic Job and Dependency Injection
using Quartz;
public class EmailReminderJob : IJob
{
private readonly IEmailSender _email;
public EmailReminderJob(IEmailSender
email) => _email = email;
public async Task Execute(IJobExecutionContext
context)
{
var jobData =
context.MergedJobDataMap;
var userId =
jobData.GetString("userId");
await _email.SendAsync(userId,
"Reminder", "This is your scheduled reminder.");
}
}
Scheduling Types
// Simple trigger: run once after 30 seconds
var simpleTrigger = TriggerBuilder.Create()
.WithIdentity("oneOffTrigger")
.StartAt(DateBuilder.FutureDate(30,
IntervalUnit.Second))
.ForJob("EmailReminderJob")
.Build();
// Cron trigger: every day at 02:30 AM
var cronTrigger = TriggerBuilder.Create()
.WithIdentity("dailyCron")
.WithCronSchedule("0 30 2 ? *
*")
.ForJob("EmailReminderJob")
.Build();
JobDataMap – Passing Parameters
var job = JobBuilder.Create<EmailReminderJob>()
.WithIdentity("EmailReminderJob-User42")
.UsingJobData("userId",
"42")
.StoreDurably()
.Build();
Durable Jobs, Recovery, Non-Concurrent Execution
[DisallowConcurrentExecution]
public class ReportJob : IJob { ... }
[PersistJobDataAfterExecution]
public class RecoverableJob : IJob { ... }
Persistence with PostgreSQL
services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
q.UsePersistentStore(store =>
{
store.UsePostgres(pg =>
{
pg.ConnectionString = "Host=localhost;Username=quartz;Password=secret;Database=quartzdb";
pg.TablePrefix =
"qrtz_";
}, dataSourceName:
"default");
store.UseProperties = true;
store.UseNewtonsoftJsonSerializer();
});
q.SchedulerName =
"ClusteredScheduler";
q.SchedulerId = "AUTO";
});
Clustering
q.UsePersistentStore(store =>
{
store.UsePostgres(...);
store.UseProperties = true;
store.Clustered = true;
store.TablePrefix =
"qrtz_";
});
Listeners (Job/Trigger/Scheduler)
public class SimpleJobListener : IJobListener
{
public string Name =>
"SimpleJobListener";
public Task
JobToBeExecuted(IJobExecutionContext context, CancellationToken ct) { /*...*/ }
public Task
JobExecutionVetoed(IJobExecutionContext context, CancellationToken ct) {
/*...*/ }
public Task
JobWasExecuted(IJobExecutionContext context, JobExecutionException? ex,
CancellationToken ct) { /*...*/ }
}
Misfire Handling
// Cron misfire handling
.WithCronSchedule("0 0 9 ? * *", cronOpts => cronOpts.WithMisfireHandlingInstructionDoNothing());
// Simple schedule misfire handling
.WithSimpleSchedule(x =>
x.WithIntervalInMinutes(5).RepeatForever().WithMisfireHandlingInstructionFireNow());
Calendars – Excluding Dates
var holidayCalendar = new HolidayCalendar();
holidayCalendar.AddExcludedDate(new DateTime(2025, 12, 25));
await scheduler.AddCalendar("holidays", holidayCalendar, true, true);
Job Management – Pause, Resume, Delete
await scheduler.PauseJob(new
JobKey("EmailReminderJob"));
await scheduler.ResumeJob(new JobKey("EmailReminderJob"));
await scheduler.DeleteJob(new JobKey("EmailReminderJob"));
Job Chaining
public class FirstJob : IJob
{
public async Task
Execute(IJobExecutionContext context)
{
var sched = context.Scheduler;
var nextJob =
JobBuilder.Create<SecondJob>().WithIdentity("Second").Build();
var trigger =
TriggerBuilder.Create().StartNow().ForJob(nextJob).Build();
await sched.ScheduleJob(nextJob,
trigger);
}
}
Custom JobFactory
public class CustomJobFactory : IJobFactory
{
private readonly IServiceProvider
_provider;
public
CustomJobFactory(IServiceProvider provider) => _provider = provider;
public IJob NewJob(TriggerFiredBundle
bundle, IScheduler scheduler)
=> (IJob)_provider.GetRequiredService(bundle.JobDetail.JobType);
public void ReturnJob(IJob job) { }
}
Serialization Options
persistenceOptions.UseNewtonsoftJsonSerializer();
persistenceOptions.UseSimpleTypeSerializer();
Long-Running Jobs with Cancellation
public class LongJob : IJob
{
public async Task
Execute(IJobExecutionContext context)
{
var ct =
context.CancellationToken;
for (int i=0; i<100; i++)
{
ct.ThrowIfCancellationRequested();
await Task.Delay(1000, ct);
}
}
}
Comments
Post a Comment