Skip to main content

IRecurringJob Interface

A no-payload job that runs on a cron schedule. Use this for periodic maintenance tasks like cache cleanup, report generation, or health checks.

Namespace: Zeridion.Flare · Assembly: Zeridion.Flare.dll

public interface IRecurringJob
{
Task ExecuteAsync(JobContext context);
}

ExecuteAsync​

Task ExecuteAsync(JobContext context);
ParameterTypeDescription
contextJobContextExecution context with job metadata, cancellation, and logging.

The contract is the same as IJob<T>.ExecuteAsync — completion means success, exceptions mean failure, and the CancellationToken on the context should be passed to all async operations.

The key difference from IJob<T> is that recurring jobs have no payload. All configuration comes from the class itself and its attributes.

Cron schedule​

Every IRecurringJob must have a [JobConfig] attribute with a CronSchedule property. The schedule uses standard 5-field cron format:

┌───────────── minute (0–59)
│ ┌───────────── hour (0–23)
│ │ ┌───────────── day of month (1–31)
│ │ │ ┌───────────── month (1–12)
│ │ │ │ ┌───────────── day of week (0–6, Sun=0)
│ │ │ │ │
* * * * *

Common patterns:

ExpressionMeaning
* * * * *Every minute
*/15 * * * *Every 15 minutes
0 * * * *Every hour, on the hour
0 3 * * *Daily at 3:00 AM UTC
0 0 * * 1Every Monday at midnight UTC
0 0 1 * *First day of every month at midnight

Auto-registration​

When the worker starts, the SDK scans assemblies for IRecurringJob implementations and registers their schedules with the Zeridion Flare API. The schedule ID is derived from the fully-qualified type name.

You do not need to call any registration method manually — AddZeridionFlare handles discovery, and the worker reports the registrations on startup.

Missed run handling​

If Flare finds that one or more scheduled runs were missed (for example, the worker was offline), it creates a single catch-up job for the most recent missed run. It does not backfill every missed interval.

Dependency injection​

Recurring job classes are registered in the DI container as their concrete type (transient). Constructor injection works normally:

[JobConfig(CronSchedule = "0 3 * * *", TimeoutSeconds = 300)]
public class CleanupExpiredSessions : IRecurringJob
{
private readonly ISessionStore _sessions;

public CleanupExpiredSessions(ISessionStore sessions) => _sessions = sessions;

public async Task ExecuteAsync(JobContext ctx)
{
var removed = await _sessions.RemoveExpiredAsync(ctx.CancellationToken);
ctx.Logger.LogInformation("Removed {Count} expired sessions", removed);
}
}

Usage example​

A recurring job that generates a daily summary report:

[JobConfig(CronSchedule = "0 6 * * *", Queue = "reports", MaxAttempts = 3)]
public class DailySummaryReport : IRecurringJob
{
private readonly IReportService _reports;
private readonly IEmailService _email;

public DailySummaryReport(IReportService reports, IEmailService email)
{
_reports = reports;
_email = email;
}

public async Task ExecuteAsync(JobContext ctx)
{
ctx.Logger.LogInformation("Generating daily summary for {Date}",
DateTimeOffset.UtcNow.Date);

var report = await _reports.GenerateDailySummaryAsync(ctx.CancellationToken);
await _email.SendReportAsync("team@example.com", report, ctx.CancellationToken);
}
}

See also​