The last update of NCronJob was some time ago - and as always, there are some new features in the meantime. So here we are, let's go through to highlight them!
If you are not familiar with what NCronJob is, here are three links:
- My first blog post about it: NCronJob - Scheduling made easy
- Our GitHub Repository: https://github.com/NCronJob-Dev/NCronJob
- Documentation: https://docs.ncronjob.dev/
Now that we are warm and ready, let's dive into the new features!
Dependent Jobs
I teased this already in the last blog post, but now it is up and running! You can now define dependent jobs, which will be executed after the parent job has finished.
It is easily done by adding the ExecuteWhen
chain:
Services.AddNCronJob(options =>
{
options.AddJob<JobB>().ExecuteWhen(
success: s => s.RunJob<SuccessJob>(),
faulted: f => f.RunJob<FaultedJob>());
});
You could even chain multiple jobs together:
Services.AddNCronJob(options =>
{
options.AddJob<JobB>().ExecuteWhen(
success: s => s.RunJob<SuccessJob>().RunJob<AnotherJob>(),
faulted: f => f.RunJob<FaultedJob>());
});
A dependent job has access to the output of the parent:
public class JobA : IJob
{
public Task ExecuteAsync(JobExecutionContext context)
{
context.Output = "Hello World";
return Task.CompletedTask;
}
}
public class JobB : IJob
{
public Task ExecuteAsync(JobExecutionContext context)
{
var parentOutput = context.ParentOutput; // "Hello World"
return Task.CompletedTask;
}
}
// Register the dependency so that JobB runs after JobA automatically
Services.AddNCronJob(options =>
{
options.AddJob<JobA>().ExecuteWhen(success: s => s.RunJob<JobB>());
});
A parent job also has the ability to cancel the dependent job:
public class JobA : IJob
{
public Task ExecuteAsync(JobExecutionContext context)
{
context.SkipChildren();
return Task.CompletedTask;
}
}
Minimal API everywhere
We introduce a new "model" that leans towards the Minimal API of ASP.NET itself. You can easily define "just" lambdas instead of defining a whole class that derives from IJob
. We allowed this behavior almost everywhere:
Job Registration
builder.Services.AddNCronJob((IMyService service, JobExecutionContext context, CancellationToken token) =>
{
service.DoSomething();
}, "*/5 * * * * *");
Also, the JobExecutionContext
is passed in as well as the CancellationToken
- so you can easily cancel the job if needed.
Instant Jobs
app.MapPost("/send-email", (RequestDto dto, IInstantJobRegistry jobRegistry) =>
{
var parameterDto = new ParameterDto
{
Email = dto.Email,
Subject = dto.Subject,
Body = dto.Body
};
jobRegistry.RunInstantJob<MyJob>(parameterDto);
return Results.Ok();
});
Here we used the Minimal API from ASP.NET to trigger an instant job via our Minimal API.
Dependent Jobs
builder.Services.AddNCronJob(options =>
{
options.AddJob<ImportDataJob>().ExecuteWhen(
success: s => s.RunJob(async (ITransfomerService transformerService) => await transformerService.TransformDataAsync()),
faulted: f => f.RunJob(async (INotificationService notificationService) => await notificationService.SendNotificationAsync()));
});
Of course, the same can be done with the Minimal API!
Startup Jobs
We introduced a new concept called "Startup Jobs". These jobs are executed once when the application starts. This is useful for jobs that need to run once when the application starts, like seeding a database or loading some configuration. They will only run once and every other job has to wait until they are finished. We block because you might rely on the startup job to be finished (like a DB Migration).
public class MyStartupJob : IJob
{
public Task RunAsync(JobExecutionContext context, CancellationToken token)
{
// Perform startup task
return Task.CompletedTask;
}
}
Services.AddNCronJob(options =>
{
options.AddJob<MyStartupJob>()
.RunAtStartup();
});
Adding, removing or updating jobs at runtime
You can now add or remove jobs at runtime. This is useful if you want to add a job based on a user's input or remove a job that is no longer needed. You can also update the schedule of a job at runtime. For that we introduced the option to name a schedule (basically a combination of job, cron expression and parameter).
app.MapPost("/add-job", (IRuntimeJobRegistry registry) =>
{
registry.AddJob(n => n.AddJob<SampleJob>(p => p.WithCronExpression("* * * * *").WithName("MyName")));
return TypedResults.Ok();
});
The outer AddJob
method has the same signature as AddNCronJob
- so you get the same builder with the same options! Nothing new to learn here.
app.MapDelete("/remove-job", (IRuntimeJobRegistry registry) =>
{
registry.RemoveJob("MyName");
return TypedResults.Ok();
});
Also by job type is possible:
app.MapDelete("/remove-job", (IRuntimeJobRegistry registry) =>
{
registry.RemoveJob<SampleJob>(); // Alternatively RemoveJob(typeof(SampleJob))
return TypedResults.Ok();
});
And here the update case:
app.MapPut("/update-job", (IRuntimeJobRegistry registry) =>
{
registry.UpdateSchedule("MyName", "* * * * *", TimeZoneInfo.Utc);
return TypedResults.Ok();
});
Smaller but noteworthy things
Besides those big changes, there are some smaller but noteworthy things:
- We moved the code from
LinkDotNet.NCronJob
toNCronJob
- so our own Organization without my prefix. It didn't really fit and now it is gone! - We have the whole documentation live under https://docs.ncronjob.dev/ - so you can easily access it!
Conclusion
NCronJob is somewhat young, but we are confident that it is a great library for scheduling jobs. We are already seeing usage and more importantly big differences to Hangfire or Quartz. Mainly this library should feel very very close to ASP.NET! The way you would configure or setup things is just "ASP".
You can leave a star on our repository - that is always appreciated! Furthermore, we introduced also devcontainers for the repository - so you can easily start developing without any hassle. Here is the quick link:
This is used for the development of the code or the documentation (which is of course also hosted in the same repository).