Quartz.NET Configuration
MassTransit supports Quartz.NET as a message scheduler, providing enterprise-grade scheduled message delivery with persistent storage. Quartz.NET is particularly useful when you need:
- Enterprise-grade reliability with database-backed storage
- Clustering support for high availability and scalability
- Complex job scheduling with cron expressions
- Fine-grained control over thread pools and job execution
- Integration with existing Quartz.NET installations
Installation
Section titled “Installation”Install the required packages:
dotnet add package MassTransit.QuartzConfigure Quartz.NET
Section titled “Configure Quartz.NET”Basic Configuration
Section titled “Basic Configuration”For development or simple deployments:
services.AddQuartz();services.AddMassTransit(x =>{ x.AddPublishMessageScheduler();
x.AddQuartzConsumers();
x.UsingRabbitMq((context, cfg) => { cfg.UsePublishMessageScheduler();
cfg.ConfigureEndpoints(context); });});Production Configuration with SQL Server
Section titled “Production Configuration with SQL Server”For production deployments, use persistent storage with SQL Server:
services.AddQuartz(q =>{ q.SchedulerName = "MassTransit-Scheduler"; q.SchedulerId = "AUTO";
q.UseDefaultThreadPool(tp => { tp.MaxConcurrency = 10; });
q.UsePersistentStore(s => { s.UseProperties = true; s.RetryInterval = TimeSpan.FromSeconds(15); s.UseSqlServer(connectionString); });});Configure MassTransit
Section titled “Configure MassTransit”services.AddMassTransit(x =>{ x.AddPublishMessageScheduler();
x.AddQuartzConsumers();
x.UsingRabbitMq((context, cfg) => { cfg.UsePublishMessageScheduler();
cfg.ConfigureEndpoints(context); });});The key components are:
AddPublishMessageScheduler()- Registers the message scheduler in the containerAddQuartzConsumers()- Adds the Quartz consumers that handle scheduling operationsUsePublishMessageScheduler()- Configures the transport to use the scheduler
Using the Message Scheduler
Section titled “Using the Message Scheduler”From a Consumer
Section titled “From a Consumer”Inject ConsumeContext and use the scheduling methods:
public class OrderProcessingConsumer : IConsumer<ProcessOrder>{ public async Task Consume(ConsumeContext<ProcessOrder> context) { // Schedule a message to be sent after a delay await context.ScheduleSend<SendNotification>( DateTime.UtcNow + TimeSpan.FromHours(1), new NotificationMessage { OrderId = context.Message.OrderId, Message = "Your order has been processed" }); }}From a Service/Controller
Section titled “From a Service/Controller”Inject IMessageScheduler from the container:
public class NotificationService{ private readonly IMessageScheduler _scheduler;
public NotificationService(IMessageScheduler scheduler) { _scheduler = scheduler; }
public async Task SendDelayedNotification(string email, string message) { await _scheduler.SchedulePublish( DateTime.UtcNow.AddDays(1), new NotificationMessage { Email = email, Message = message }); }}ScheduleSend vs SchedulePublish
Section titled “ScheduleSend vs SchedulePublish”ScheduleSend- Sends directly to a specific endpoint (useful for targeting a specific queue)SchedulePublish- Publishes to the message bus (useful for pub/sub scenarios)
// Send to a specific endpointawait context.ScheduleSend( new Uri("queue:notification-service"), delay, message);
// Publish for subscribersawait context.SchedulePublish(delay, message);Recurring Messages
Section titled “Recurring Messages”Quartz.NET supports recurring jobs using cron expressions:
public class DailyReportSchedule : DefaultRecurringSchedule{ public DailyReportSchedule() { ScheduleId = "daily-report"; CronExpression = "0 0 9 * * ?"; // Every day at 9 AM }}
public record GenerateDailyReport;To schedule a recurring message:
public class ReportSchedulerService{ private readonly IRecurringMessageScheduler _scheduler;
public ReportSchedulerService(IRecurringMessageScheduler scheduler) { _scheduler = scheduler; }
public async Task ScheduleDailyReports() { var inputAddress = new Uri("queue:report-generator");
await _scheduler.ScheduleRecurringSend( inputAddress, new DailyReportSchedule(), new GenerateDailyReport()); }}To cancel a recurring schedule:
await _scheduler.CancelScheduledRecurringMessage("daily-report", null);Clustering
Section titled “Clustering”Quartz.NET supports clustering for high availability and scalability. When clustered:
- Multiple scheduler instances share the same database
- Jobs are load-balanced across nodes
- Automatic fail-over when a node goes down
Configure Clustering
Section titled “Configure Clustering”services.AddQuartz(q =>{ q.SchedulerName = "MassTransit-Scheduler"; q.SchedulerId = "AUTO";
q.UseDefaultThreadPool(tp => { tp.MaxConcurrency = 10; });
q.UsePersistentStore(s => { s.UseProperties = true; s.PerformSchemaValidation = true; s.RetryInterval = TimeSpan.FromSeconds(15);
s.UseSqlServer(connectionString);
s.UseClustering(c => { c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20); c.CheckinInterval = TimeSpan.FromSeconds(10); }); });});Scheduled Redelivery
Section titled “Scheduled Redelivery”Quartz.NET can be used for scheduled message redelivery (second-level retries):
services.AddMassTransit(x =>{ x.AddConsumer<OrderProcessingConsumer>();
x.AddConfigureEndpointsCallback((context, name, cfg) => { cfg.UseScheduledRedelivery(r => r .Intervals( TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(30)));
cfg.UseMessageRetry(r => r.Immediate(3)); });
x.UsingRabbitMq((context, cfg) => { cfg.UsePublishMessageScheduler(); cfg.ConfigureEndpoints(context); });});Full ASP.NET Core Example
Section titled “Full ASP.NET Core Example”var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("quartz") ?? throw new InvalidOperationException("Connection string 'quartz' is not configured.");
// Configure Quartz with SQL Server and clusteringbuilder.Services.AddQuartz(q =>{ q.SchedulerName = "MassTransit-Scheduler"; q.SchedulerId = "AUTO";
q.UseDefaultThreadPool(tp => { tp.MaxConcurrency = 10; });
q.UsePersistentStore(s => { s.UseProperties = true; s.RetryInterval = TimeSpan.FromSeconds(15); s.UseSqlServer(connectionString);
s.UseClustering(c => { c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20); c.CheckinInterval = TimeSpan.FromSeconds(10); }); });});
// Add MassTransit with Quartzbuilder.Services.AddMassTransit(x =>{ x.AddPublishMessageScheduler(); x.AddConsumers(typeof(Program).Assembly); x.AddQuartzConsumers();
x.UsingRabbitMq((context, cfg) => { cfg.Host(builder.Configuration.GetConnectionString("RabbitMq"));
cfg.UsePublishMessageScheduler(); cfg.ConfigureEndpoints(context); });});
// Start Quartz schedulerbuilder.Services.AddQuartzHostedService(options =>{ options.StartDelay = TimeSpan.FromSeconds(5); options.WaitForJobsToComplete = true;});
var app = builder.Build();
app.UseRouting();app.UseAuthorization();app.MapControllers();app.MapHealthChecks("/health/ready");
app.Run();appsettings.json:
{ "ConnectionStrings": { "quartz": "Server=.;Database=Quartz;Trusted_Connection=True;TrustServerCertificate=true", "RabbitMq": "rabbitmq://localhost:5672" }}Samples
Section titled “Samples”- Sample-Quartz - Official MassTransit Quartz.NET sample