Quickstart

Get BackWave running in a .NET app. Install the package, wire AddBackWave, declare a [Job], enqueue it, and watch it run.


This Quickstart takes a fresh .NET app to a running background job in a few minutes. You'll add the package, register BackWave against the In-Memory Store (zero infrastructure), declare a job with the [Job] attribute, and enqueue it. Everything here mirrors the real sample API. None of it is invented.

The In-Memory Store needs no infrastructure, but it isn't durable. Jobs live in process memory and are lost on restart, and it runs on a single host. It's a fine fit for tests, local dev, and simple single-host services. When you need durability or to span nodes, point BackWave at a Storage Adapter over a database you already run (see Installation and Configuration).

1. Install the package#

Terminal
dotnet add package BackWave
dotnet add package BackWave.Hosting

BackWave carries the Core and the job model; BackWave.Hosting wires the Worker Groups into the generic host. A source generator ships in the box. It turns your [Job] methods into payload records, handlers, and a registry, so there is no hand-written serialization or registration to keep in sync.

2. Declare a Job#

A job is a method annotated with [Job("wire-name")]. The Wire Name is the job's stable identity in storage. It never derives from the CLR type name, so renaming the class never changes it. The source generator emits everything else from this signature.

EmailJobs.cs
using BackWave.Jobs;
 
public sealed class EmailJobs(ILogger<EmailJobs> logger)
{
    [Job("send-welcome", Queue = "default")]
    public Task SendWelcomeAsync(string email, JobContext context, CancellationToken ct)
    {
        logger.LogInformation(
            "send-welcome: {Email} (job {JobId}, attempt {Attempt})",
            email, context.JobId, context.Attempt);
        return Task.CompletedTask;
    }
}

The class that declares [Job] methods is resolved from DI per Attempt, so anything you inject (here an ILogger) is available to every job body. Because delivery is at-least-once, a handler may run more than once; idempotency is the handler author's responsibility.

3. Wire up BackWave#

Register BackWave in your host. Point it at a store, hand it the generated jobs module, and declare a Worker Group, one pool of Workers that serves your queues.

Program.cs
using BackWave;
using BackWave.Core;
using BackWave.Generated;
using BackWave.Storage.InMemory;
 
var builder = WebApplication.CreateBuilder(args);
 
builder.Services.AddBackWave(backwave =>
{
    backwave
        .UseStore(_ => new InMemoryJobStore())
        .UseJobs(BackWaveJobs.Module);
 
    backwave.AddWorkerGroup(new WorkerGroupOptions
    {
        Name = "default",
        Policy = new DispatchPolicy.Strict(["default"]),
        PollInterval = TimeSpan.FromMilliseconds(250),
        RetryPolicy = new RetryPolicy { MaxAttempts = 5 },
    });
});
 
var app = builder.Build();

UseJobs(BackWaveJobs.Module) is the single call that registers the Job Registry, a scoped handler per [Job], and the scoped class that declares them (EmailJobs). AddWorkerGroup runs an in-process pump as a hosted service. There's no separate worker host to deploy.

4. Enqueue work#

Inject BackWaveClient and enqueue a job by constructing the generated payload. The Wire Name routes it to the right handler; the dueTime decides when it runs (a job whose due time is now is just an immediate enqueue).

Program.cs
app.MapPost("/welcome", async (BackWaveClient client, string email) =>
{
    var jobId = await client.EnqueueAsync(
        new SendWelcome(email),
        dueTime: DateTimeOffset.UtcNow);
 
    return Results.Ok(new { jobId });
});
 
app.Run();

5. Run it#

Terminal
dotnet run

POST to /welcome?email=you@example.com and the Worker Group claims the job, runs SendWelcomeAsync, and logs the line. You now have a working background job system with no broker to provision.

Where to go next#

  • Installation: package references, prerequisites, and selecting a Storage Adapter for production.
  • Configuration: Worker Group options, Dispatch Policy, retries, leases, and Concurrency Limits.
  • Virtual-Time Testing: drive the runtime deterministically from a test.