Skip to main content

IJob<T> Interface

The core abstraction for payload-based background jobs. Implement this interface to define a unit of work that processes a typed payload in the background.

Namespace: Zeridion.Flare · Assembly: Zeridion.Flare.dll

public interface IJob<in TPayload> where TPayload : class
{
Task ExecuteAsync(TPayload payload, JobContext context);
}

ExecuteAsync

Task ExecuteAsync(TPayload payload, JobContext context);
ParameterTypeDescription
payloadTPayloadThe deserialized payload for this job.
contextJobContextExecution context with job metadata, cancellation, and logging.

Contract:

  • Success — when ExecuteAsync completes without throwing, the job is marked as Succeeded.
  • Failure — when ExecuteAsync throws any exception, the job is marked as failed. If retries remain, it is rescheduled with exponential backoff. If all attempts are exhausted, it moves to DeadLetter.
  • Cancellation — throw OperationCanceledException (or let it propagate from context.CancellationToken) to signal cancellation. The worker treats this the same as a failure for retry purposes.

Payload constraints

The TPayload type parameter has a class constraint — it must be a reference type. The payload is serialized to JSON when the job is enqueued and deserialized when the worker picks it up.

Serialization uses System.Text.Json with snake_case naming (JsonNamingPolicy.SnakeCaseLower), case-insensitive deserialization, and null-value ignoring. Your payload class should be a simple POCO or record:

public record NewUserEvent
{
public string Email { get; init; } = "";
public string Name { get; init; } = "";
public string UserId { get; init; } = "";
}
tip

Use record types with init properties for payloads. They are immutable, concise, and serialize cleanly.

Dependency injection

Job classes are resolved from the DI container on every execution. The SDK registers each discovered IJob<T> implementation as transient, so you get a fresh instance for each job run. Constructor injection works normally:

public class SendWelcomeEmail : IJob<NewUserEvent>
{
private readonly IEmailService _email;
private readonly ILogger<SendWelcomeEmail> _logger;

public SendWelcomeEmail(IEmailService email, ILogger<SendWelcomeEmail> logger)
{
_email = email;
_logger = logger;
}

public async Task ExecuteAsync(NewUserEvent payload, JobContext ctx)
{
await _email.SendAsync(payload.Email, "Welcome!", ctx.CancellationToken);
ctx.Logger.LogInformation("Welcome email sent to {Email}", payload.Email);
}
}

The SDK discovers job classes by scanning assemblies at startup. By default it scans the entry assembly. To scan additional assemblies, set JobAssemblies in ZeridionFlareOptions.

Configuring defaults

Apply the [JobConfig] attribute to set class-level defaults for queue, retries, and timeout:

[JobConfig(Queue = "email", MaxAttempts = 5, TimeoutSeconds = 120)]
public class SendWelcomeEmail : IJob<NewUserEvent>
{
// ...
}

These defaults can be overridden per-call with JobOptions. See Option precedence for the full resolution order.

Usage example

public class GenerateThumbnail : IJob<ImageUploadEvent>
{
private readonly IImageProcessor _processor;
private readonly IBlobStorage _storage;

public GenerateThumbnail(IImageProcessor processor, IBlobStorage storage)
{
_processor = processor;
_storage = storage;
}

public async Task ExecuteAsync(ImageUploadEvent payload, JobContext ctx)
{
ctx.Logger.LogInformation("Generating thumbnail for {ImageId}, attempt {Attempt}",
payload.ImageId, ctx.AttemptNumber);

var image = await _storage.DownloadAsync(payload.OriginalUrl, ctx.CancellationToken);
var thumbnail = await _processor.ResizeAsync(image, 200, 200, ctx.CancellationToken);
await _storage.UploadAsync($"thumbs/{payload.ImageId}.jpg", thumbnail, ctx.CancellationToken);
}
}

public record ImageUploadEvent
{
public string ImageId { get; init; } = "";
public string OriginalUrl { get; init; } = "";
}

See also