WJb 0.108.0
dotnet add package WJb --version 0.108.0
NuGet\Install-Package WJb -Version 0.108.0
<PackageReference Include="WJb" Version="0.108.0" />
<PackageVersion Include="WJb" Version="0.108.0" />
<PackageReference Include="WJb" />
paket add WJb --version 0.108.0
#r "nuget: WJb, 0.108.0"
#:package WJb@0.108.0
#addin nuget:?package=WJb&version=0.108.0
#tool nuget:?package=WJb&version=0.108.0
⚡ WJb — Explicit Job Execution Engine
If you can’t explain why a job runs, you don’t control your system.
🚀 Quick Start
This is the fastest way to understand how WJb works.
Workflow
send-email → log → done
Code
using WJb;
var store = new InMemoryStore();
var wjb = WJbBuilder.Create(store, cfg =>
{
cfg.AddAction<SendEmailAction>(Actions.SendEmail);
cfg.AddAction<LogAction>(Actions.Log);
cfg.AddService(new SmtpSettings
{
Host = "smtp.local"
});
});
await wjb.EnqueueAsync(Actions.SendEmail, new EmailInput
{
To = "user@test.com"
});
await wjb.ExecuteLoopAsync();
public static class Actions
{
public const string SendEmail = "send-email";
public const string Log = "log";
}
public sealed class SendEmailAction(SmtpSettings smtp) : JobAction<EmailInput>
{
private readonly SmtpSettings _smtp = smtp;
public override Task<ActionResult> ExecuteAsync(EmailInput input, CancellationToken ct)
{
Console.WriteLine($"Email sent to {input.To} via {_smtp.Host}");
return Task.FromResult(ActionResults.Next(
new JobCommand(
Actions.Log,
new LogInput { Message = $"Email sent to {input.To}" })));
}
}
public sealed class LogAction : JobAction<LogInput>
{
public override Task<ActionResult> ExecuteAsync(LogInput input, CancellationToken ct)
{
Console.WriteLine(input.Message);
return Task.FromResult(ActionResults.None());
}
}
public sealed class EmailInput
{
public string? To { get; set; }
}
public sealed class LogInput
{
public string? Message { get; set; }
}
public sealed class SmtpSettings
{
public string Host { get; set; } = default!;
}
What this demonstrates
- Actions contain business logic
- Actions support constructor injection
- Actions define the workflow
- Services are resolved automatically
- Jobs execute through a store-backed runtime
- Workflows are explicit and deterministic
💥 The Problem
Many background job systems eventually become:
Job
↓
Retry
↓
Pipeline
↓
Middleware
↓
???
Soon it becomes difficult to answer:
- Why did this job run?
- Why was it retried?
- Who scheduled the next step?
- What happens under the hood?
✅ The WJb Way
Job
↓
Executor
↓
Action
↓
ActionResult
↓
JobCommand
- Explicit execution
- Explicit workflows
- Explicit retries
- No hidden pipelines
- No hidden orchestration
- No magic
🧠 Core Concepts
Action
An action contains business logic.
public sealed class SendEmailAction : JobAction<SendEmailInput>
{
public const string Key = "send-email";
public override Task<ActionResult> ExecuteAsync(SendEmailInput input, CancellationToken ct)
{
Console.WriteLine($"Sending email to {input.To}");
return Task.FromResult(ActionResults.None());
}
}
ActionResult
Defines what happens next.
Nothing else:
return ActionResults.None();
Schedule another job:
return ActionResults.Next(
new JobCommand(
LogAction.Key,
new LogInput { Message = "Email sent" }));
JobCommand
Represents the next step.
new JobCommand(
LogAction.Key,
new LogInput { Message = "Done" })
No hidden workflow engine.
The action decides what happens next.
🏭 Factory
Actions can be created explicitly.
var wjb = WJbBuilder.Create(cfg =>
{
cfg.AddAction<LogAction>(LogAction.Key);
});
var action = await wjb.CreateAsync(LogAction.Key);
await action.ExecuteAsync(new LogInput
{
Message = "Hello WJb 👋"
},
CancellationToken.None);
WJb uses explicit action keys, making workflows predictable and easy to trace.
📦 Configuring Services
Actions support constructor injection.
var wjb = WJbBuilder.Create(cfg =>
{
cfg.AddService(new SmtpSettings
{
Host = "localhost"
});
cfg.AddAction<ConfiguredEmailAction>(ConfiguredEmailAction.Key);
});
public sealed class ConfiguredEmailAction(SmtpSettings settings) : JobAction<SendEmailInput>
{
public const string Key = "configured-email";
public override Task<ActionResult> ExecuteAsync(SendEmailInput input, CancellationToken ct)
{
Console.WriteLine(settings.Host);
return Task.FromResult(ActionResults.None());
}
}
⚙️ Background Job Execution
Configure WJb:
var store = new InMemoryStore();
var wjb = WJbBuilder.Create(store, cfg =>
{
cfg.AddAction<SendEmailAction>(SendEmailAction.Key);
});
Queue a job:
await wjb.EnqueueAsync(
SendEmailAction.Key,
new SendEmailInput { To = "test@test.com" });
Execute a single cycle:
await wjb.ExecuteOnceAsync();
Or execute continuously:
await wjb.ExecuteLoopAsync();
🔗 Workflows
Actions explicitly define the next step.
public sealed class AAction : JobAction<EmptyInput>
{
public const string Key = "a";
public override Task<ActionResult> ExecuteAsync(EmptyInput input, CancellationToken ct)
{
return Task.FromResult(ActionResults.Next(
new JobCommand(BAction.Key, new EmptyInput())));
}
}
public sealed class BAction : JobAction<EmptyInput>
{
public const string Key = "b";
public override Task<ActionResult> ExecuteAsync(EmptyInput input, CancellationToken ct)
{
return Task.FromResult(ActionResults.None());
}
}
A → B
No separate workflow engine required.
🔄 Retries
Retries are configured per job.
await wjb.EnqueueAsync(
RetryAction.Key,
new EmptyInput(),
new JobOptions
{
MaxRetries = 3,
RetryDelay = TimeSpan.FromSeconds(10)
});
Attempt 1
↓
Failed
↓
Retry Job Created
↓
Attempt 2
↓
Completed
🆚 Why WJb?
| Feature | WJb |
|---|---|
| Explicit workflows | ✅ |
| Explicit retries | ✅ |
| Constructor injection | ✅ |
| Typed actions | ✅ |
| Background execution | ✅ |
| Hidden pipelines | ❌ |
| Hidden orchestration | ❌ |
| Magic execution | ❌ |
✅ Use WJb If
You want:
- predictable execution
- explicit workflows
- strongly typed actions
- business-driven orchestration
- simple background processing
- unit-test-friendly design
❌ WJb Is Not For You If
You want:
- convention-based magic
- invisible pipelines
- hidden orchestration
- workflow definitions hidden in configuration
- execution that is difficult to trace
⚡ TL;DR
Action = Business Logic
ActionResult = Outcome
JobCommand = Next Step
Executor = Runner
Store = Persistence
You always know:
- why a job started
- what it did
- what it scheduled next
- why it was retried
Need Help?
The author provides professional services for:
- WJb implementation and integration
- Custom Action development
- SQL Server setup and optimization
- Workflow design
- Architecture reviews
- Migration from existing schedulers
- Production support and troubleshooting
Contact:
📧 ukrguru@gmail.com
Please include:
- Company name
- Project description
- Expected workload or requirements
🎁 Support WJb
If WJb changed how you think about background jobs:
🎉 Early Support Bonus
Any donation before August 1, 2026 qualifies for:
👉 FREE Solo License
WJb exists because background jobs shouldn't be magic.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on WJb:
| Package | Downloads |
|---|---|
|
WJb.UI.Blazor
Free Blazor monitoring and administration UI for WJb. Includes jobs monitoring, actions management, services configuration, payload inspection and action testing. |
GitHub repositories
This package is not used by any popular GitHub repositories.
WJb 0.108.0
- Simplified fluent API with IActionFactory, IJobExecutor and IWJb
- Create(...) and CreateAsync(...) bootstrapping
- Action-based workflow engine
- Action chaining through ActionResult.Next(...)
- Typed inputs and automatic payload binding
- Constructor-based dependency injection
- Definition store for actions and services
- Store-driven configuration with AddActionAsync(...) and AddServiceAsync(...)
- Job scheduling and delayed execution
- Retry support and dead-letter handling
- Progress reporting and job cancellation
- In-memory and SQL-based storage providers
- JobsTab monitoring UI component
- Deterministic and unit-test-friendly architecture
- Improved API surface and project structure