Add Background Jobs
Granit.BackgroundJobs provides durable, cluster-safe recurring job scheduling built
on Wolverine. Jobs are plain Wolverine messages decorated with [RecurringJob] —
no special interfaces to implement, no duplicate executions in multi-node deployments.
Prerequisites
Section titled “Prerequisites”- A working Granit application with
Granit.Wolverineconfigured Granit.Wolverine.Postgresqlfor transactional outbox (production)
1. Install the packages
Section titled “1. Install the packages”dotnet add package Granit.BackgroundJobsFor production persistence and administration endpoints:
dotnet add package Granit.BackgroundJobs.EntityFrameworkCoredotnet add package Granit.BackgroundJobs.Endpoints2. Register the module
Section titled “2. Register the module”using Granit.Core.Modularity;using Granit.BackgroundJobs;using Granit.BackgroundJobs.EntityFrameworkCore;using Granit.BackgroundJobs.Endpoints;
[DependsOn( typeof(GranitBackgroundJobsModule), typeof(GranitBackgroundJobsEntityFrameworkCoreModule), typeof(GranitBackgroundJobsEndpointsModule))]public sealed class MyAppModule : GranitModule { }using Granit.Core.Modularity;using Granit.BackgroundJobs;
[DependsOn(typeof(GranitBackgroundJobsModule))]public sealed class MyAppModule : GranitModule { }3. Configure the store mode
Section titled “3. Configure the store mode”{ "BackgroundJobs": { "Mode": "Durable" }}| Mode | Package | Behavior |
|---|---|---|
InMemory | Granit.BackgroundJobs | State lost on restart. Use for development and tests. |
Durable | Granit.BackgroundJobs.EntityFrameworkCore | State persisted in PostgreSQL/SQL Server. Use for production. |
4. Define a recurring job
Section titled “4. Define a recurring job”A recurring job consists of a message class with the [RecurringJob] attribute and
a standard Wolverine handler:
[RecurringJob("0 8 * * *", "daily-report")]public sealed class GenerateDailyReportCommand;
public static class GenerateDailyReportHandler{ public static async Task Handle( GenerateDailyReportCommand command, IReportService reportService, CancellationToken cancellationToken) { await reportService.GenerateAsync(cancellationToken); // The middleware automatically schedules the next occurrence after this returns. }}The first argument is a Cronos cron expression. The second is a unique job name used for administration (pause, resume, trigger).
5. Cron expression reference
Section titled “5. Cron expression reference”Granit uses Cronos for cron parsing. Both 5-field (no seconds) and 6-field (with seconds) formats are supported.
| Expression | Meaning |
|---|---|
0 * * * * | Every hour at minute 0 |
0 8 * * * | Every day at 08:00 UTC |
0 8 * * 1 | Every Monday at 08:00 UTC |
*/5 * * * * | Every 5 minutes |
0 0 1 * * | First of each month at 00:00 UTC |
0 */30 * * * * | Every 30 seconds (6-field) |
All occurrences are calculated in UTC. If you need tenant-local time, use
ICurrentTimezoneProvider inside the handler.
6. Map administration endpoints
Section titled “6. Map administration endpoints”app.MapBackgroundJobsEndpoints();Or with custom options:
app.MapBackgroundJobsEndpoints(opts =>{ opts.RoutePrefix = "admin/jobs"; opts.RequiredRole = "ops-team";});Available routes
Section titled “Available routes”| Method | Route | Description |
|---|---|---|
GET | /{prefix} | List all jobs with status |
GET | /{prefix}/{name} | Detail of a single job |
POST | /{prefix}/{name}/pause | Suspend scheduling |
POST | /{prefix}/{name}/resume | Resume scheduling |
POST | /{prefix}/{name}/trigger | Execute immediately (202 Accepted) |
All endpoints require the BackgroundJobs.Jobs.Manage permission.
7. Administer jobs programmatically
Section titled “7. Administer jobs programmatically”Inject IBackgroundJobManager to control jobs from your own code:
public sealed class MaintenanceService(IBackgroundJobManager jobManager){ public async Task PauseReportingAsync(CancellationToken cancellationToken) { await jobManager.PauseAsync("daily-report", cancellationToken); }
public async Task TriggerNowAsync(CancellationToken cancellationToken) { await jobManager.TriggerNowAsync("daily-report", cancellationToken); }}Job status
Section titled “Job status”IBackgroundJobManager.GetAllAsync() returns a list of BackgroundJobStatus records:
public sealed record BackgroundJobStatus( string JobName, string CronExpression, bool IsEnabled, DateTimeOffset? LastExecutedAt, DateTimeOffset? NextExecutionAt, int ConsecutiveFailures, long DeadLetterCount, string? LastError);Security
Section titled “Security”Endpoint authorization
Section titled “Endpoint authorization”The endpoints are protected by the BackgroundJobs.Jobs.Manage permission. In
production, grant this permission using one of two approaches:
{ "Authorization": { "AdminRoles": ["admin", "ops-team"] }}await permissionManager.SetAsync( "BackgroundJobs.Jobs.Manage", "ops-team", tenantId: null, isGranted: true);Audit trail
Section titled “Audit trail”Manual triggers via TriggerNowAsync inject an X-Triggered-By header with the
operator’s user ID. This is recorded in the job store for ISO 27001 traceability.
Next steps
Section titled “Next steps”- Set up notifications to alert users when jobs complete
- Implement webhooks to notify external systems of job results
- Granit.BackgroundJobs reference for the full API surface and store interfaces