.NET React Templates
Development

Add a background job

Use ServiceStack Background Jobs for work that is slow, retryable, scheduled, or should survive the initiating request.

Development recipes · Background processing

1. Define a small command payload

Persist business state first, then enqueue only the organization and stable identifiers:

public class ProcessWidget
{
    public string WorkspaceId { get; set; } = default!;
    public string WidgetId { get; set; } = default!;
}

[Worker("widgets")]
public class ProcessWidgetCommand(IDbConnectionFactory dbFactory)
    : AsyncCommand<ProcessWidget>
{
    protected override async Task RunAsync(
        ProcessWidget request, CancellationToken token)
    {
        // Confined to the organization, and records the job's name in audit columns
        using var db = dbFactory.OpenForWorkspace(request.WorkspaceId, "widget-job");

        // Null if the widget isn't this organization's
        var widget = db.SingleById<Widget>(request.WidgetId)
            ?? throw new InvalidOperationException("Widget was not found.");

        if (widget.Status == WidgetStatus.Completed)
            return;

        await ProcessAsync(widget, token);
        widget.Status = WidgetStatus.Completed;
        db.Update(widget);   // ModifiedDate and ModifiedBy are set by the connection
    }
}

Do not serialize uploads, secrets, large documents, sessions, or an open database connection into a job request.

Idempotent Background Job Recovery

2. Register and enqueue

Register the command in MyApp/Configure.BackgroundJobs.cs:

services.AddTransient<ProcessWidgetCommand>();

Enqueue after the referenced row exists:

jobs.EnqueueForWorkspace<ProcessWidgetCommand>(scope.Workspace.Id,
    new ProcessWidget { WorkspaceId = scope.Workspace.Id, WidgetId = widget.Id });

Use a named [Worker] when the workload should have an independent queue/concurrency boundary.

3. Add recurring work only when needed

Register recurring commands after the application host initializes:

jobs.RecurringCommand<RepairWidgetsCommand>(Schedule.Hourly);

Recurring jobs must be safe when two ticks overlap or a prior run partially completed. Query bounded batches and checkpoint durable progress.

Reliability rules

  • Make the handler idempotent and return successfully when work is already complete.
  • Re-read authoritative state inside the job; do not trust stale enqueue-time decisions.
  • Put the organization in the payload and open the connection with dbFactory.OpenForWorkspace(workspaceId, "job-name"). A job that processes every organization opens a confined connection for each, or opens one with dbFactory.OpenAcrossWorkspaces("job-name") and runs one statement with explicit conditions. A guard test fails when a connection is opened with dbFactory.Open().
  • Use provider idempotency keys for external side effects.
  • Store attempts/status/error on domain or inbox records when operators need recovery visibility.
  • Compensate quota reservations when terminal work fails.
  • Pass and honor cancellation tokens.

Use /admin-ui for general job inspection and /admin/operations for product-specific failed queues and safe retries.

Verify

Test successful execution, duplicate delivery, missing/already-complete records, transient failure and retry, terminal failure, cancellation, tenant isolation, quota compensation, and recurring overlap. Run the application long enough to exercise the real hosted worker.

On this page