v5 Migration Guide¶
This document describes the changes made in v5 of NCronJob and how to migrate from v4.
Supported target frameworks¶
net8.0 and net9.0 are out of support, so NCronJob and NCronJob.Dashboard no longer target them. Both packages target net10.0 and net11.0.
If your application still targets net8.0 or net9.0, upgrade it to net10.0 or later, or stay on v4:
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
Typed job parameters with IJob<TParameter>¶
IJobExecutionContext.Parameter (object?) is removed. A job that accepts a parameter implements IJob<TParameter> and reads it, typed, from IJobExecutionContext<TParameter>.Parameter. A job that implements the plain IJob doesn’t accept a parameter anymore.
- public class ReportJob : IJob
+ public class ReportJob : IJob<ReportRequest>
{
- public Task RunAsync(IJobExecutionContext context, CancellationToken token)
+ public Task RunAsync(IJobExecutionContext<ReportRequest> context, CancellationToken token)
{
- var request = (ReportRequest)context.Parameter!;
+ var request = context.Parameter;
...
}
}
The registration and trigger APIs (WithParameter, RunInstantJob, RunScheduledJob, ForceRun*, RunJob<TJob>(parameter), UpdateParameter) keep their signatures, but now check the parameter. A parameter that isn’t assignable to TParameter, or any parameter for a plain IJob, throws an ArgumentException when the job is registered or triggered, instead of failing inside the job.
What to do:
- For every job that reads
context.Parameter, pick the parameter type and implementIJob<TParameter>. Prefer a dedicatedrecordover primitives. - If the parameter is optional, declare it nullable (
IJob<ReportRequest?>). A run without a parameter getsdefault(TParameter). - If a job really accepts parameters of different types, implement
IJob<object?>. It behaves like anIJobin v4. -
Delegate jobs that read the parameter declare
IJobExecutionContext<TParameter>instead ofIJobExecutionContext:diff - builder.Services.AddNCronJob((IJobExecutionContext context) => Console.WriteLine(context.Parameter), "* * * * *"); + builder.Services.AddNCronJob((IJobExecutionContext<string> context) => Console.WriteLine(context.Parameter), "* * * * *"); -
Notification and exception handlers that read the parameter pattern-match the context:
if (context is IJobExecutionContext<ReportRequest> reportContext). - Unit tests that mock
IJobExecutionContext.ParametermockIJobExecutionContext<TParameter>instead and call the typedRunAsync. - Registrations that passed a parameter to a parameterless job now throw at startup. Remove the parameter or implement
IJob<TParameter>.
JobConditionContext.Parameter and ExecutionProgress.Parameter stay object?, because they describe a run rather than feed a job.
The dashboard deserializes the JSON of a triggered job into TParameter. JSON that doesn’t follow the structure of TParameter (unknown properties, missing constructor parameters, null for non-nullable properties) is rejected. Jobs without a parameter show no parameter field.
Removed obsolete APIs¶
All APIs that were marked [Obsolete] in v4 are removed.
DateTimeOffset overloads of IInstantJobRegistry¶
RunScheduledJob<TJob>(DateTimeOffset, ...), RunScheduledJob(string, DateTimeOffset, ...), RunScheduledJob(Delegate, DateTimeOffset, ...) and ForceRunScheduledJob(Delegate, DateTimeOffset, ...) are removed. Pass a delay instead:
- jobRegistry.RunScheduledJob<MyJob>(startAt, parameter);
+ jobRegistry.RunScheduledJob<MyJob>(startAt - timeProvider.GetUtcNow(), parameter);
A delay that lies in the past behaves like a DateTimeOffset in the past did.
Custom implementations of IInstantJobRegistry no longer implement these members.
IStartupStage and AddJob<TJob>().RunAtStartup()¶
IStartupStage<TJob> is removed. AddJob returns an INotificationStage<TJob>. Configure startup jobs inside the options of AddJob:
- options.AddJob<MyJob>().RunAtStartup();
+ options.AddJob<MyJob>(j => j.RunAtStartup(shouldCrashOnFailure: false));
Mind the default: IStartupStage.RunAtStartup() defaulted to shouldCrashOnFailure: false, while the builder’s RunAtStartup() defaults to true, so a failing startup job stops the application. Pass false to keep the v4 behaviour.
A cron job that also runs at startup chains RunAtStartup after the cron expression:
- options.AddJob<MyJob>(j => j.WithCronExpression("0 * * * *")).RunAtStartup();
+ options.AddJob<MyJob>(j => j.WithCronExpression("0 * * * *").RunAtStartup(shouldCrashOnFailure: false));
WithName goes before WithCronExpression¶
JobOptionBuilder.WithCronExpression returns the same builder as WithName(...).WithCronExpression(...) already did: a ParameterAndRunAtStartupBuilder. The legacy ParameterBuilder is removed. After a cron expression you can add a parameter, a startup trigger, a condition, a timeout, an expiry or SkipIfStillRunning, but no name. Move WithName in front of the cron expression:
- p.WithCronExpression("0 * * * *").WithName("hourly")
+ p.WithName("hourly").WithCronExpression("0 * * * *")
- p.WithCronExpression("0 * * * *").WithParameter("report").WithName("hourly")
+ p.WithName("hourly").WithCronExpression("0 * * * *").WithParameter("report")
WithParameter can be called only once per cron expression. Calling it twice used to overwrite the first value, so drop the earlier call. A cron job can now also run at startup: p.WithCronExpression("0 * * * *").RunAtStartup().
RecurringJobSchedule has no optional constructor parameters¶
Type and IsTypedJob of RecurringJobSchedule no longer have default values. This only affects code that constructs a RecurringJobSchedule itself, for example a fake IRuntimeJobRegistry in tests:
- new RecurringJobSchedule("name", "* * * * *", true, TimeZoneInfo.Utc);
+ new RecurringJobSchedule("name", "* * * * *", true, TimeZoneInfo.Utc, Type: null, IsTypedJob: false);
The observer API is stable¶
IJobExecutionProgressReporter, ExecutionProgress and ExecutionState are no longer marked [Experimental("NCRONJOB_OBSERVER")]. They now follow the semver lifecycle of NCronJob. Remove the suppressions you added for them:
- <NoWarn>$(NoWarn);NCRONJOB_OBSERVER</NoWarn>
- dotnet_diagnostic.NCRONJOB_OBSERVER.severity = none