skills/infra/background-jobs/SKILL.md
Use when adding background jobs, recurring tasks, or Hangfire scheduled work.
npx skillsauth add faysilalshareef/dotnet-ai-kit background-jobsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
// Program.cs
builder.Services.AddHangfire(config => config
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(connectionString));
builder.Services.AddHangfireServer(options =>
{
options.WorkerCount = Environment.ProcessorCount * 2;
});
var app = builder.Build();
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = [new HangfireAuthorizationFilter()]
});
namespace {Company}.{Domain}.Infrastructure.Jobs;
public sealed class DailyReportJob(
IReportService reportService,
ILogger<DailyReportJob> logger)
{
public async Task ExecuteAsync()
{
logger.LogInformation("Generating daily report");
await reportService.GenerateDailyReportAsync();
}
}
// Registration
RecurringJob.AddOrUpdate<DailyReportJob>(
"daily-report",
job => job.ExecuteAsync(),
Cron.Daily(2, 0)); // 2:00 AM
// In a controller or handler — enqueue work after response
BackgroundJob.Enqueue<IEmailService>(
service => service.SendOrderConfirmationAsync(orderId));
namespace {Company}.{Domain}.Infrastructure;
public sealed class DatabaseMigrationService(
IServiceScopeFactory scopeFactory,
ILogger<DatabaseMigrationService> logger) : IHostedService
{
public async Task StartAsync(CancellationToken ct)
{
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
logger.LogInformation("Applying database migrations");
await db.Database.MigrateAsync(ct);
}
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}
namespace {Company}.{Domain}.Infrastructure;
public sealed class OutboxCleanupService(
IServiceScopeFactory scopeFactory,
ILogger<OutboxCleanupService> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider
.GetRequiredService<ApplicationDbContext>();
var cutoff = DateTime.UtcNow.AddDays(-7);
var deleted = await db.OutboxMessages
.Where(m => m.PublishedAt != null && m.PublishedAt < cutoff)
.ExecuteDeleteAsync(ct);
if (deleted > 0)
logger.LogInformation("Cleaned up {Count} outbox messages", deleted);
}
catch (Exception ex)
{
logger.LogError(ex, "Outbox cleanup failed");
}
await Task.Delay(TimeSpan.FromHours(1), ct);
}
}
}
| Anti-Pattern | Correct Approach | |---|---| | In-memory job scheduling | Use Hangfire with persistent store | | Fire-and-forget without tracking | Use Hangfire for visibility and retry | | Missing error handling in background | Wrap in try/catch, log errors | | Blocking thread pool in hosted service | Use async/await throughout |
# Find Hangfire usage
grep -r "Hangfire\|RecurringJob\|BackgroundJob" --include="*.cs" src/
# Find BackgroundService implementations
grep -r ": BackgroundService" --include="*.cs" src/
# Find IHostedService
grep -r ": IHostedService" --include="*.cs" src/
AddHostedService<T> for hosted servicesdata-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.