WJb Docs - Intermediate
Learn workflows, persistence, scheduling, and production-ready patterns.
π Start Here
π§© Mental Model
π Building Workflows
π₯ Failures & Control
π Observation & Debugging
π Practical Patterns
β Working Examples
β‘οΈ Whatβs Next
β‘ Create & Register Actions
Actions contain the business logic of your application.
Every unit of work in WJb is an Action.
Creating an Action
Inherit from JobAction:
public sealed class SendEmailAction : JobAction<EmailInput>
{
public override async Task<IActionResult> ExecuteAsync(
EmailInput input, CancellationToken ct)
{
// send email logic here
return await CompleteAsync();
}
}
The generic parameter TInput defines the strongly typed payload the action expects.
Input Model
public sealed class EmailInput
{
public string To { get; init; } = string.Empty;
public string Subject { get; init; } = string.Empty;
public string Body { get; init; } = string.Empty;
}
WJb automatically converts the job payload into this type.
Action Name
You can give the action an explicit name:
[ActionName("send-email")]
public sealed class SendEmailAction : JobAction<EmailInput>
{
// ...
}
This name is used when enqueueing jobs and when creating next commands.
Registering Actions
Use WJbBuilder:
var wjb = WJbBuilder.Create(store, cfg =>
{
cfg.AddAction<SendEmailAction>(); // uses ActionName or type name
// or
cfg.AddAction<SendEmailAction>("send-email");
});
You can also register multiple actions or load them from JSON / a definition store.
Enqueueing a Job
Once registered, you can start a job:
await wjb.EnqueueAsync("send-email",
new EmailInput
{
To = "user@test.com",
Subject = "Hello",
Body = "Welcome!"
});
or using the type:
await wjb.EnqueueAsync<SendEmailAction>(
new EmailInput { ... });
Dependency Injection
Actions support constructor injection:
public sealed class SendEmailAction(IEmailService email)
: JobAction<EmailInput>
{
public override async Task<IActionResult> ExecuteAsync(
EmailInput input, CancellationToken ct)
{
await email.SendAsync(input.To, input.Subject, input.Body, ct);
return await CompleteAsync();
}
}
Register the required services with the same builder or the DI container.
Minimal Complete Example
[ActionName("send-email")]
public sealed class SendEmailAction : JobAction<EmailInput>
{
public override Task<IActionResult> ExecuteAsync(
EmailInput input, CancellationToken ct)
{
Console.WriteLine($"Sending email to {input.To}");
return CompleteAsync();
}
}
// Registration
var wjb = WJbBuilder.Create(store, cfg =>
{
cfg.AddAction<SendEmailAction>();
});
// Usage
await wjb.EnqueueAsync("send-email",
new EmailInput { To = "user@test.com" });
Best Practices
- Keep one clear responsibility per action
- Prefer small, focused input models
- Always pass the
CancellationToken - Use
[ActionName]for stable keys - Prefer constructor injection over service locator
Key Point
An action is just a class that receives input, does work, and returns a result.
Everything else in WJb builds on this simple foundation.