Skip to main content
Version: 1.1

11. Execution Context

11.1 The contract

public interface IBackgroundJobExecutionContext
{
BackgroundJobId JobId { get; }
string? RecurringJobId { get; }
Guid? TenantId { get; }
Guid? UserId { get; }
string? Queue { get; }
BackgroundJobTriggerType TriggerType { get; }
string? CorrelationId { get; }
BackgroundJobPriority Priority { get; }
CancellationToken CancellationToken { get; }
}

Every property is read-only — a context is fully specified at construction and never mutated afterward.

11.2 Every property, explained

PropertyTypeMeaning
JobIdBackgroundJobId (readonly record struct wrapping string)Unique identifier for this specific execution instance
RecurringJobIdstring?The Hangfire recurring-job ID this execution belongs to (e.g. "campaign-cleanup-pipeline"), null for a one-off fire-and-forget execution
TenantIdGuid?Which tenant this execution is scoped to; null means host-level (all-tenant) — see §8 — Tenant Context
UserIdGuid?The user who triggered the execution, if any (e.g. a manually-triggered job); null for system-scheduled recurring executions
Queuestring?The Hangfire queue this execution runs on, if a non-default queue is used
TriggerTypeBackgroundJobTriggerTypeFireAndForget=1, Delayed=2, Recurring=3, Continuation=4
CorrelationIdstring?An operator-facing tracing identifier, threaded through logs for this execution
PriorityBackgroundJobPriorityLow=1, Normal=2, High=3, Critical=4
CancellationTokenCancellationTokenThe token propagated all the way from the Hangfire bridge into the job's RunAsync call

11.3 The concrete implementation

BackgroundJobExecutionContext (public, sealed class, in Core) implements the interface. Its constructor signature is frozen under the framework's LTS backward-compatibility policy — adding a required parameter to it would be a breaking, MAJOR-version-only change:

public BackgroundJobExecutionContext(
BackgroundJobId jobId,
BackgroundJobTriggerType triggerType,
string? recurringJobId = null,
Guid? tenantId = null,
Guid? userId = null,
string? queue = null,
string? correlationId = null,
BackgroundJobPriority priority = BackgroundJobPriority.Normal,
CancellationToken cancellationToken = default)

Only jobId and triggerType are required — every other property has a sensible default, keeping the constructor usable for the simplest case (a host-level, unscoped, normal-priority fire-and-forget job) while still allowing every dimension to be specified explicitly.

11.4 Lifecycle

Creation: a BackgroundJobExecutionContext is constructed exactly once per execution, inside the Hangfire bridge (the I{Job}PipelineJob implementation), immediately before the pipeline is invoked. It is never reused across executions — even for the same recurring job, each tick gets a brand-new context with a fresh JobId.

Ownership: the context is owned by whichever call constructed it (the bridge) for the duration of a single RunAsync/ExecuteAsync/pipeline call chain. It flows by reference through IBackgroundJobExecutionPipeline.RunAsyncIBackgroundJobExecutor.ExecuteAsync → the job's own IBackgroundJob.RunAsync — every layer receives the same context instance, never a copy.

Disposal: the context implements no IDisposable/IAsyncDisposable — it is a plain immutable data holder with no unmanaged resources of its own. It is simply discarded (garbage-collected) once the execution chain returns.

11.5 Public API surface

Only the interface (IBackgroundJobExecutionContext, in Abstractions) and the one concrete implementation (BackgroundJobExecutionContext, in Core) are public. There is no factory/builder type — callers construct the concrete class directly. See §12.7 for a complete worked construction example.