ronnythedev/dotnet-clean-architecture-skills/15-dotnet-quartz-background-jobs

Claude Code and agent skill by ronnythedev/dotnet-clean-architecture-skills

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Generates scheduled background jobs using Quartz.NET. Includes job definitions, triggers, cron scheduling, dependency injection, and persistent job store configuration.

Install

npx vskill@latest install ronnythedev/dotnet-clean-architecture-skills/15-dotnet-quartz-background-jobs

SKILL.md

Background Job Generator (Quartz)

Overview

Quartz.NET is a full-featured job scheduling library:

  • Job scheduling - Run tasks at specific times or intervals
  • Cron expressions - Complex scheduling patterns
  • Persistence - Jobs survive application restarts
  • Dependency injection - Full DI support
  • Clustering - Distributed job execution

Quick Reference

ComponentPurpose
IJobJob interface to implement
IConfigureOptions<QuartzOptions>Job registration
JobKeyUnique job identifier
TriggerBuilderDefines when job runs
CronScheduleBuilderCron-based scheduling
SimpleScheduleBuilderInterval-based scheduling

Job Structure

/Infrastructure/
├── BackgroundJobs/
│ ├── {JobName}Job.cs
│ ├── {JobName}JobSetup.cs
│ └── ...
└── DependencyInjection.cs

Template: Simple Interval Job

csharp
// src/{name}.infrastructure/BackgroundJobs/ProcessPendingOrdersJob.cs
using Microsoft.Extensions.Logging;
using Quartz;
namespace {name}.infrastructure.backgroundjobs;
/// <summary>
/// Processes pending orders every 5 minutes
/// </summary>
[DisallowConcurrentExecution] // Prevent overlapping executions
public sealed class ProcessPendingOrdersJob : IJob
{
private readonly IOrderRepository _orderRepository;
private readonly IOrderProcessor _orderProcessor;
private readonly ILogger<ProcessPendingOrdersJob> _logger;
public ProcessPendingOrdersJob(
IOrderRepository orderRepository,
IOrderProcessor orderProcessor,
ILogger<ProcessPendingOrdersJob> logger)
{
_orderRepository = orderRepository;
_orderProcessor = orderProcessor;
_logger = logger;
}
public async Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Starting pending orders processing...");
try
{
var pendingOrders = await _orderRepository
.GetPendingOrdersAsync(context.CancellationToken);
_logger.LogInformation(
"Found {Count} pending orders to process",
pendingOrders.Count);
foreach (var order in pendingOrders)
{
try
{
await _orderProcessor.ProcessAsync(order, context.CancellationToken);
_logger.LogInformation(
"Processed order {OrderId}",
order.Id);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Failed to process order {OrderId}",
order.Id);
}
}
_logger.LogInformation("Completed pending orders processing");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in pending orders processing job");
throw; // Quartz will handle retry based on configuration
}
}
}

Template: Job Setup (IConfigureOptions)

csharp
// src/{name}.infrastructure/BackgroundJobs/ProcessPendingOrdersJobSetup.cs
using Microsoft.Extensions.Options;
using Quartz;
namespace {name}.infrastructure.backgroundjobs;
internal sealed class ProcessPendingOrdersJobSetup
: IConfigureOptions<QuartzOptions>
{
public void Configure(QuartzOptions options)
{
var jobKey = JobKey.Create(nameof(ProcessPendingOrdersJob));
options
.AddJob<ProcessPendingOrdersJob>(jobBuilder =>
jobBuilder
.WithIdentity(jobKey)
.WithDescription("Processes pending orders"))
.AddTrigger(triggerBuilder =>
triggerBuilder
.ForJob(jobKey)
.WithIdentity($"{nameof(ProcessPendingOrdersJob)}-trigger")
.WithSimpleSchedule(schedule =>
schedule
.WithIntervalInMinutes(5)
.RepeatForever())
.StartNow());
}
}

Template: Cron Scheduled Job

csharp
// src/{name}.infrastructure/BackgroundJobs/DailyReportJob.cs
using Microsoft.Extensions.Logging;
using Quartz;
namespace {name}.infrastructure.backgroundjobs;
/// <summary>
/// Generates daily reports at 6:00 AM every day
/// </summary>
[DisallowConcurrentExecution]
public sealed class DailyReportJob : IJob
{
private readonly IReportService _reportService;
private readonly IEmailService _emailService;
private readonly ILogger<DailyReportJob> _logger;
public DailyReportJob(
IReportService reportService,
IEmailService emailService,
ILogger<DailyReportJob> logger)
{
_reportService = reportService;
_emailService = emailService;
_logger = logger;
}
public async Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Starting daily report generation...");
var reportDate = DateTime.UtcNow.Date.AddDays(-1);
var report = await _reportService.GenerateDailyReportAsync(
reportDate,
context.CancellationToken);
await _emailService.SendReportAsync(
report,
context.CancellationToken);
_logger.LogInformation(
"Daily report for {Date} sent successfully",
reportDate.ToShortDateString());
}
}
csharp
// src/{name}.infrastructure/BackgroundJobs/DailyReportJobSetup.cs
using Microsoft.Extensions.Options;
using Quartz;
namespace {name}.infrastructure.backgroundjobs;
internal sealed class DailyReportJobSetup : IConfigureOptions<QuartzOptions>
{
public void Configure(QuartzOptions options)
{
var jobKey = JobKey.Create(nameof(DailyReportJob));
options
.AddJob<DailyReportJob>(jobBuilder =>
jobBuilder
.WithIdentity(jobKey)
.WithDescription("Daily report generation"))
.AddTrigger(triggerBuilder =>
triggerBuilder
.ForJob(jobKey)
.WithIdentity($"{nameof(DailyReportJob)}-trigger")
.WithCronSchedule(
"0 0 6 * * ?", // 6:00 AM every day
builder => builder.InTimeZone(TimeZoneInfo.Utc))
.WithDescription("Fires at 6:00 AM UTC daily"));
}
}

Cron Expression Reference

ExpressionDescription
0 0 * * * ?Every hour at minute 0
0 0/15 * * * ?Every 15 minutes
0 0 6 * * ?Daily at 6:00 AM
0 0 6 ? * MON-FRIWeekdays at 6:00 AM
0 0 0 1 * ?First day of month at midnight
0 0 0 L * ?Last day of month at midnight
0 0 12 ? * SUNEvery Sunday at noon

Format: seconds minutes hours day-of-month month day-of-week [year]

FieldValues
Seconds0-59
Minutes0-59
Hours0-23
Day-of-month1-31, L (last), W (weekday)
Month1-12 or JAN-DEC
Day-of-week1-7 or SUN-SAT, L (last)
YearOptional, 1970-2099

Special Characters:

  • * - All values
  • ? - No specific value (day-of-month/day-of-week)
  • - - Range (e.g., MON-FRI)
  • , - List (e.g., MON,WED,FRI)
  • / - Increment (e.g., 0/15 = every 15)
  • L - Last (e.g., last day of month)
  • W - Nearest weekday
  • # - Nth day (e.g., 2#3 = third Monday)

Template: Job with Data Map

csharp
// src/{name}.infrastructure/BackgroundJobs/SendScheduledEmailJob.cs
using Microsoft.Extensions.Logging;
using Quartz;
namespace {name}.infrastructure.backgroundjobs;
/// <summary>
/// Sends a scheduled email using data from JobDataMap
/// </summary>
public sealed class SendScheduledEmailJob : IJob
{
public const string EmailIdKey = "EmailId";
public const string RecipientKey = "Recipient";
private readonly IEmailService _emailService;
private readonly ILogger<SendScheduledEmailJob> _logger;
public SendScheduledEmailJob(
IEmailService emailService,
ILogger<SendScheduledEmailJob> logger)
{
_emailService = emailService;
_logger = logger;
}
public async Task Execute(IJobExecutionContext context)
{
// Get data from job data map
var dataMap = context.MergedJobDataMap;
var emailId = dataMap.GetGuid(EmailIdKey);
var recipient = dataMap.GetString(RecipientKey);
_logger.LogInformation(
"Sending scheduled email {EmailId} to {Recipient}",
emailId,
recipient);
await _emailService.SendScheduledEmailAsync(
emailId,
context.CancellationToken);
}
}
// Scheduling the job with data
public class EmailScheduler
{
private readonly ISchedulerFactory _schedulerFactory;
public async Task ScheduleEmailAsync(
Guid emailId,
string recipient,
DateTime sendAt)
{
var scheduler = await _schedulerFactory.GetScheduler();
var jobKey = new JobKey($"email-{emailId}", "scheduled-emails");
var job = JobBuilder.Create<SendScheduledEmailJob>()
.WithIdentity(jobKey)
.UsingJobData(SendScheduledEmailJob.EmailIdKey, emailId.ToString())
.UsingJobData(SendScheduledEmailJob.RecipientKey, recipient)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity($"email-{emailId}-trigger", "scheduled-emails")
.StartAt(sendAt)
.Build();
await scheduler.ScheduleJob(job, trigger);
}
}

Template: Job with Retry Logic

csharp
// src/{name}.infrastructure/BackgroundJobs/SyncExternalDataJob.cs
using Microsoft.Extensions.Logging;
using Quartz;
namespace {name}.infrastructure.backgroundjobs;
/// <summary>
/// Syncs data from external API with retry support
/// </summary>
[DisallowConcurrentExecution]
[PersistJobDataAfterExecution] // Persist data map changes
public sealed class SyncExternalDataJob : IJob
{
private const int MaxRetries = 3;
private const string RetryCountKey = "RetryCount";
private readonly IExternalApiClient _apiClient;
private readonly IDataSyncService _syncService;
private readonly ILogger<SyncExternalDataJob> _logger;
public SyncExternalDataJob(
IExternalApiClient apiClient,
IDataSyncService syncService,
ILogger<SyncExternalDataJob> logger)
{
_apiClient = apiClient;
_syncService = syncService;
_logger = logger;
}
public async Task Execute(IJobExecutionContext context)
{
var retryCount = context.MergedJobDataMap.GetInt(RetryCountKey);
try
{
_logger.LogInformation(
"Starting external data sync (attempt {Attempt})",
retryCount + 1);
var data = await _apiClient.FetchDataAsync(context.CancellationToken);
await _syncService.SyncAsync(data, context.CancellationToken);
// Reset retry count on success
context.JobDetail.JobDataMap.Put(RetryCountKey, 0);
_logger.LogInformation("External data sync completed successfully");
}
catch (Exception ex)
{
_logger.LogError(
ex,
"External data sync failed (attempt {Attempt} of {MaxRetries})",
retryCount + 1,
MaxRetries);
if (retryCount < MaxRetries - 1)
{
// Increment retry count
context.JobDetail.JobDataMap.Put(RetryCountKey, retryCount + 1);

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
64
Repository forks
11
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/ronnythedev/dotnet-clean-architecture-skills/15-dotnet-quartz-background-jobs/badge)](https://verified-skill.com/skills/ronnythedev/dotnet-clean-architecture-skills/15-dotnet-quartz-background-jobs)

Something wrong with this skill or its page? Tell us.