Your first background job in .NET with NexJob
Sooner or later a web app needs to do something that should not block the request: send an e-mail, resize an image, call a slow API. This post builds the smallest possible background job in .NET 8 and runs it. No database, no extra services, about five minutes.
What you need
- The .NET 8 SDK
- A terminal
1. A project
dotnet new worker -n MyJobs
cd MyJobs
dotnet add package NexJob
A worker service is the simplest host. Everything below works the same inside an ASP.NET Core app.
2. Register NexJob
Two lines in Program.cs: one to add NexJob, one to let it find your job classes.
using NexJob;
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddNexJob();
services.AddNexJobJobs(typeof(Program).Assembly);
})
.Build();
Call AddNexJob() once. Calling it twice registers the background services twice.
3. Write the job
A job is a class with one method. Implement IJob when the work needs no input.
public sealed class HelloJob : IJob
{
public Task ExecuteAsync(CancellationToken ct)
{
Console.WriteLine($"Hello at {DateTimeOffset.UtcNow:O}");
return Task.CompletedTask;
}
}
Two things worth knowing right away:
- NexJob builds the job from your dependency injection container, so constructor injection works like anywhere else in .NET.
- The
CancellationTokenis yours to honor. When the host shuts down, it is how the job finds out.
4. Enqueue it
Ask the container for IScheduler and enqueue the job.
await host.StartAsync();
var scheduler = host.Services.GetRequiredService<IScheduler>();
await scheduler.EnqueueAsync<HelloJob>();
await host.WaitForShutdownAsync();
5. Run it
dotnet run
Hello at 2026-10-13T12:33:27.9831829+00:00
That is the whole loop. The job was stored, picked up by a worker running inside your process, and executed. There is no separate worker process to deploy.
A job that takes input
Most real jobs need data. Implement IJob<T> and use a record for the input: it serializes cleanly and cannot change after it is enqueued.
public sealed record SendWelcomeEmailInput(string Email, string UserName);
public sealed class SendWelcomeEmailJob : IJob<SendWelcomeEmailInput>
{
private readonly IEmailService _email;
public SendWelcomeEmailJob(IEmailService email) => _email = email;
public async Task ExecuteAsync(SendWelcomeEmailInput input, CancellationToken ct)
=> await _email.SendAsync(input.Email, $"Welcome, {input.UserName}!", ct);
}
await scheduler.EnqueueAsync<SendWelcomeEmailJob, SendWelcomeEmailInput>(
new SendWelcomeEmailInput("user@example.com", "Jane"));
What happens if the process stops?
With the default storage, jobs live in memory and are gone when the process exits. That is fine for trying things out and for tests. For anything real, add a database provider:
dotnet add package NexJob.Postgres
services.AddNexJobPostgres("Host=localhost;Database=nexjob;Username=postgres;Password=secret");
services.AddNexJob();
Register the provider before AddNexJob(), so it replaces the in-memory default. SQL Server, Redis and MongoDB providers exist too.
Where to go next
- Retries and what happens when a job keeps failing
- Delays, recurring jobs and deadlines
- Seeing what your jobs are doing (the next posts in this series)
Source and samples: https://github.com/oluciano/NexJob
Documentation: https://oluciano.github.io/NexJob/
Top comments (0)