Background job patterns with Hangfire, hosted services, and recurring tasks. Covers job scheduling, persistent store, and fire-and-forget patterns. Trigger: background job, Hangfire, recurring task, scheduled job, cron.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
// 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 services