Skip to content

Background Jobs

SimpleModule provides a background job system built on TickerQ for scheduling and executing long-running tasks outside the HTTP request pipeline. Jobs support progress reporting, structured logging, retries, and CRON-based recurring schedules.

Defining a Job

Implement IModuleJob and register it in your module:

csharp
public class GenerateReportJob : IModuleJob
{
    public async Task ExecuteAsync(
        IJobExecutionContext context,
        CancellationToken cancellationToken)
    {
        var data = context.GetData<ReportRequest>();
        context.ReportProgress(0, "Starting report generation");

        // Do work...
        context.Log("Processing 500 records");
        context.ReportProgress(50, "Halfway done");

        // More work...
        context.ReportProgress(100, "Report complete");
    }
}

Register the job in your module's ConfigureServices:

csharp
services.AddModuleJob<GenerateReportJob>();

Scheduling Jobs

Inject IBackgroundJobs to enqueue, schedule, or create recurring jobs:

Immediate Execution

csharp
var jobId = await backgroundJobs.EnqueueAsync<GenerateReportJob>(
    new ReportRequest { Type = "monthly" });

Delayed Execution

csharp
var jobId = await backgroundJobs.ScheduleAsync<GenerateReportJob>(
    executeAt: DateTimeOffset.UtcNow.AddHours(1),
    data: new ReportRequest { Type = "monthly" });

Recurring Jobs (CRON)

csharp
var recurringId = await backgroundJobs.AddRecurringAsync<GenerateReportJob>(
    name: "Monthly Report",
    cronExpression: "0 0 1 * *",  // 1st of every month at midnight
    data: new ReportRequest { Type = "monthly" });

CRON expressions use the standard 5-field format: minute hour day month dayOfWeek.

Managing Jobs

csharp
// Cancel a running or pending job
await backgroundJobs.CancelAsync(jobId);

// Check job status
var status = await backgroundJobs.GetStatusAsync(jobId);

// Toggle a recurring job on/off
await backgroundJobs.ToggleRecurringAsync(recurringId);

// Remove a recurring job
await backgroundJobs.RemoveRecurringAsync(recurringId);

Job Execution Context

During execution, IJobExecutionContext provides:

MethodPurpose
GetData<T>()Deserialize the job's input data
ReportProgress(percentage, message?)Update progress (0-100) with optional message
Log(message)Add a timestamped log entry
JobIdThe unique identifier for this execution

Progress updates are batched and flushed to the database periodically (configurable via ProgressFlushInterval).

Job States

StateDescription
PendingQueued, waiting to execute
RunningCurrently executing
CompletedFinished successfully
FailedExecution threw an exception
CancelledCancelled by user or system
SkippedSkipped (e.g., overlapping recurring run)

API Endpoints

All endpoints require BackgroundJobs.ViewJobs or BackgroundJobs.ManageJobs permissions.

MethodRouteDescription
GET/api/jobs/List jobs with optional state/type filters
GET/api/jobs/{id}Job detail with logs and progress
POST/api/jobs/{id}/cancelCancel a pending or running job
POST/api/jobs/{id}/retryRetry a failed job
GET/api/jobs/recurringList all recurring jobs
POST/api/jobs/recurring/{id}/toggleEnable/disable a recurring job
DELETE/api/jobs/recurring/{id}Delete a recurring job

Admin UI

The module includes four admin pages at /admin/jobs:

  • Dashboard -- overview with active, failed, and recurring job counts
  • Job List -- paginated, filterable list of all jobs with state and progress
  • Job Detail -- real-time progress bar, logs, error messages, and retry/cancel actions (auto-refreshes every 2 seconds while running)
  • Recurring Jobs -- manage recurring schedules with toggle and delete actions

Configuration

csharp
public class BackgroundJobsModuleOptions : IModuleOptions
{
    public int MaxConcurrency { get; set; } = Environment.ProcessorCount;
    public int ProgressFlushBatchSize { get; set; } = 50;
    public TimeSpan ProgressFlushInterval { get; set; } = TimeSpan.FromSeconds(2);
    public int MaxLogEntries { get; set; } = 1000;
}

Contract Interface

Query job data from other modules via IBackgroundJobsContracts:

csharp
public interface IBackgroundJobsContracts
{
    Task<PagedResult<JobSummaryDto>> GetJobsAsync(JobFilter filter, CancellationToken ct);
    Task<JobDetailDto?> GetJobDetailAsync(JobId id, CancellationToken ct);
    Task<IReadOnlyList<RecurringJobDto>> GetRecurringJobsAsync(CancellationToken ct);
    Task RetryAsync(JobId id, CancellationToken ct);
}

Next Steps

  • Modules -- module structure and service registration
  • Permissions -- protecting job management endpoints
  • Events -- triggering jobs from event handlers

Released under the MIT License.