Skip to main content

Job Configuration

This page covers how to configure job behavior using attributes and how to access job data inside a handler via JobContext.

JobContext

Every handler receives a JobContext in HandleAsync. It provides access to the job's payload, metadata, and identity:

PropertyDescription
job.IdThe unique identifier of the job
job.MsgDataThe data payload (arguments) sent when the job was scheduled
job.MetadataNon-business data (e.g., correlation IDs, tracking tags)
public async Task HandleAsync(JobContext job)
{
var name = job.MsgData.TryGetStringValue("Name") ?? "World";
var source = job.Metadata.TryGetStringValue("Source");

Console.WriteLine($"[{job.Id}] Hello {name} from {source}");
}

Message Data

MsgData is the typed key-value payload attached to a job. You write it at the call site and read it inside the handler.

Writing — build the payload before scheduling:

var msg = WriteableMessageData.New()
.SetStringValue("UserEmail", "john@example.com")
.SetIntValue("RetryCount", 1)
.SetLongValue("OrderId", 9876543210L)
.SetBoolValue("SendReceipt", true)
.SetDecimalValue("Amount", 99.99m)
.SetDateTimeValue("ScheduledAt", DateTime.UtcNow);

await scheduler.OnceNowAsync<NotificationHandler>(msg);

Reading — access values inside HandleAsync:

MethodReturnsBehaviour
GetStringValue("key")stringThrows if key is missing
TryGetStringValue("key")string?Returns null if missing
GetIntValue("key")intThrows if key is missing
TryGetIntValue("key")int?Returns null if missing
GetLongValue("key")longThrows if key is missing
GetBoolValue("key")boolThrows if key is missing
GetDecimalValue("key")decimalThrows if key is missing
GetDateTimeValue("key")DateTimeThrows if key is missing
public async Task HandleAsync(JobContext job)
{
var email = job.MsgData.GetStringValue("UserEmail");
var amount = job.MsgData.TryGetDecimalValue("Amount") ?? 0m;
}

JSON Objects

Use SetJson<T> / GetJson<T> to store and retrieve complex objects. The type must have a parameterless constructor.

public class OrderPayload
{
public int OrderId { get; set; }
public string CustomerEmail { get; set; } = "";
public decimal Total { get; set; }
}

// Writing
var msg = WriteableMessageData.New()
.SetJson("Order", new OrderPayload { OrderId = 42, CustomerEmail = "john@example.com", Total = 99.99m });

// Reading
public async Task HandleAsync(JobContext job)
{
var order = job.MsgData.GetJson<OrderPayload>("Order");
var optional = job.MsgData.TryGetJson<OrderPayload>("Order"); // returns null if missing
}

Dependency Injection

Each job execution runs in its own DI scope. JobMaster resolves handler instances and their dependencies from that scope, so scoped services work correctly — each job gets a fresh set of resolved services. Inject your services directly into the constructor:

public class NotificationHandler : IJobHandler
{
private readonly IEmailService _emailService;

public NotificationHandler(IEmailService emailService)
{
_emailService = emailService;
}

public async Task HandleAsync(JobContext job)
{
var email = job.MsgData.GetStringValue("UserEmail");
await _emailService.SendAsync(email, "Your report is ready!");
}
}

Attributes

Use attributes on the handler class to control how the cluster treats your jobs.

JobDefinitionId

Defines the stable identity of the job. Defaults to the class full name.

[JobMasterDefinitionId("HelloJob")]
public sealed class HelloJobHandler : IJobHandler
warning

Always define a static DefinitionId. If you rename the class or move it to a different namespace without one, the cluster will fail to map existing persisted jobs to the new code.

Timeout

Maximum time the job is allowed to run before being forcefully terminated.

[JobMasterTimeout(10)] // seconds
public sealed class HelloJobHandler : IJobHandler

Max Retries

How many times the cluster should retry the job on failure before marking it as Failed.

[JobMasterMaxNumberOfRetries(3)]
public sealed class HelloJobHandler : IJobHandler

Priority

Influences execution order and the share of worker resources the job receives.

[JobMasterPriority(JobMasterPriority.High)]
public sealed class HelloJobHandler : IJobHandler

Worker Lane

A lane is a named execution channel that isolates a group of jobs to a dedicated set of workers. Jobs without a lane assigned compete for the same workers.

By assigning a lane, you ensure that slow or resource-intensive jobs have their own dedicated workers and never block unrelated workloads.

[JobMasterWorkerLane("PaymentsProcessing")]
public sealed class HelloJobHandler : IJobHandler

Metadata

Attaches extra information to the job for categorization, auditing, or custom logic.

[JobMasterMetadata("Category", "Payroll")]
public sealed class HelloJobHandler : IJobHandler

Configuration Hierarchy

Job configuration is resolved in order of precedence — the first value found wins:

Setting1st (highest)2nd3rd (fallback)
PriorityEnqueue call parameter[JobMasterPriority] attributeMedium
TimeoutEnqueue call parameter[JobMasterTimeout] attributeCluster DefaultJobTimeout
Max RetriesEnqueue call parameter[JobMasterMaxNumberOfRetries] attributeCluster DefaultMaxOfRetryCount
Worker LaneEnqueue call parameter[JobMasterWorkerLane] attributeNo lane

Cluster-level defaults (DefaultJobTimeout, DefaultMaxOfRetryCount) are configured in the Cluster Configuration.

This means you can define sensible defaults on the handler class and selectively override them at the call site when needed:

[JobMasterPriority(JobMasterPriority.Low)]
[JobMasterTimeout(30)]
public sealed class ReportHandler : IJobHandler { ... }

// Uses handler defaults (Low priority, 30s timeout)
await scheduler.OnceNowAsync<ReportHandler>(msg);

// Overrides priority only — timeout still comes from the attribute
await scheduler.OnceNowAsync<ReportHandler>(
msg,
priority: JobMasterPriority.Critical
);