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:
| Property | Description |
|---|---|
job.Id | The unique identifier of the job |
job.MsgData | The data payload (arguments) sent when the job was scheduled |
job.Metadata | Non-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:
| Method | Returns | Behaviour |
|---|---|---|
GetStringValue("key") | string | Throws if key is missing |
TryGetStringValue("key") | string? | Returns null if missing |
GetIntValue("key") | int | Throws if key is missing |
TryGetIntValue("key") | int? | Returns null if missing |
GetLongValue("key") | long | Throws if key is missing |
GetBoolValue("key") | bool | Throws if key is missing |
GetDecimalValue("key") | decimal | Throws if key is missing |
GetDateTimeValue("key") | DateTime | Throws 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
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:
| Setting | 1st (highest) | 2nd | 3rd (fallback) |
|---|---|---|---|
| Priority | Enqueue call parameter | [JobMasterPriority] attribute | Medium |
| Timeout | Enqueue call parameter | [JobMasterTimeout] attribute | Cluster DefaultJobTimeout |
| Max Retries | Enqueue call parameter | [JobMasterMaxNumberOfRetries] attribute | Cluster DefaultMaxOfRetryCount |
| Worker Lane | Enqueue call parameter | [JobMasterWorkerLane] attribute | No 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
);