Skip to main content

NCronJob - It got a dashboard and v5

11/10/2026
5 minute read

After quite some time, we are back with a new major version of NCronJob. Of course also ready for the freshly released dotnet 11.

Dashboard#

The first thing is: We finally have a dashboard. With that you can easily see dependencies, what jobs are running or just create new instant jobs and trigger them. It lives in a separate package: NCronJob.Dashboard that you can install. The setup is quite minimal:

csharp
builder.Services.AddNCronJob(/* your jobs */);
builder.Services.AddNCronJobDashboard();

var app = builder.Build();
app.MapNCronJobDashboard("/ncronjob", endpoints => endpoints.RequireAuthorization("Admin"));

As seen, you can easily allow a different route or have your custom endpoint authorization in place. Here a few views:

graph.webp

jobs.webp

runs.webp

dotnet 11 ready#

v5 drops the old and now eol frameworks net8.0 and net9.0 and only supports >=net10.0. Of course also we opted-in into the new async runtime feature. In general since the last time, the performance metrics are much better. Less wall time, less RAM (don't let Sam know that!)

Typed parameters#

Until now, a parameter was just an object? on the IJobExecutionContext. You could pass anything, and you only found out it was wrong when the job ran and the cast blew up:

csharp
public class ReportJob : IJob
{
    public Task RunAsync(IJobExecutionContext context, CancellationToken token)
    {
        var request = (ReportRequest)context.Parameter!; // InvalidCastException if someone passed a string
        // ...
    }
}

With v5, a job that takes a parameter says so in its type: it implements IJob<TParameter> and gets a typed context.Parameter:

csharp
public sealed record ReportRequest(string Region, IReadOnlyList<string> Recipients);

public class ReportJob : IJob<ReportRequest>
{
    public Task RunAsync(IJobExecutionContext<ReportRequest> context, CancellationToken token)
    {
        var region = context.Parameter.Region; // no cast, no surprises
        // ...
        return Task.CompletedTask;
    }
}

Registration and triggering look the same as before:

csharp
builder.Services.AddNCronJob(options =>
    options.AddJob<ReportJob>(j => j
        .WithName("weekly-report")
        .WithCronExpression("0 8 * * 1")
        .WithParameter(new ReportRequest("EU", ["[email protected]"]))));

app.MapPost("/report", (IInstantJobRegistry registry) =>
    registry.RunInstantJob<ReportJob>(new ReportRequest("US", ["[email protected]"])));

The big difference is when a wrong parameter shows up. NCronJob now checks it at registration or trigger time and throws an ArgumentException right there, instead of letting the job fail somewhere in the background:

csharp
registry.RunInstantJob<ReportJob>("weekly");
// ArgumentException: Job '...ReportJob' expects a parameter of type '...ReportRequest' but got 'System.String'.

That applies everywhere a parameter goes in: WithParameter, RunInstantJob / RunScheduledJob / ForceRun*, dependent jobs via RunJob<TJob>(parameter), and IRuntimeJobRegistry.UpdateParameter. A plain IJob doesn't accept a parameter at all anymore, so passing one to it is an error as well.

Notification handlers can get at the typed parameter, too:

csharp
if (context is IJobExecutionContext<ReportRequest> report)
{
    logger.LogInformation("Report for {Region} done", report.Parameter.Region);
}

And in the dashboard#

This is where the types pay off. When you trigger a job from the dashboard, the JSON you enter is deserialized into TParameter. Unknown properties, missing constructor parameters or null for a non-nullable property are rejected before the job is started. The input field even shows the JSON schema of the parameter type as a hint, so you know what to type. Jobs without a parameter simply don't show a parameter field.

Migrating#

For most jobs it's a three-line change:

diff
- public class ReportJob : IJob
+ public class ReportJob : IJob<ReportRequest>
  {
-     public Task RunAsync(IJobExecutionContext context, CancellationToken token)
+     public Task RunAsync(IJobExecutionContext<ReportRequest> context, Cancell
      {
-         var request = (ReportRequest)context.Parameter!;
+         var request = context.Parameter;

Migration Guide#

The v5 migration guide has all the details, including the other breaking changes in this release.

An error has occurred. This application may no longer respond until reloaded.Reload x