Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Teach IInstantJobRegistry to accept named jobs #215

Merged
merged 4 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ dotnet_diagnostic.CA1054.severity = none # CA1054: URI-like parameters should no
dotnet_diagnostic.CA1055.severity = none # CA1055: Uri return values should not be strings
dotnet_diagnostic.CA1056.severity = none # CA1056: Uri properties should not be strings
dotnet_diagnostic.CA1515.severity = none # CA1515: Because an application's API isn't typically referenced from outside the assembly, types can be made internal
dotnet_diagnostic.CA1716.severity = none # CA1716: Identifiers should not match keywords
dotnet_diagnostic.CA1812.severity = none # CA1812: Avoid uninstantiated internal classes
dotnet_diagnostic.CA2201.severity = suggestion # CA2201: Do not raise reserved exception types

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ All notable changes to **NCronJob** will be documented in this file. The project

## [Unreleased]

### Added

- Make IInstantJobRegistry accept job names. By [@nulltoken](https://github.com/nulltoken) in [#184](https://github.com/NCronJob-Dev/NCronJob/pull/215).

### Fixed

- Fix injection of context in dynamic jobs. By [@nulltoken](https://github.com/nulltoken) in [#184](https://github.com/NCronJob-Dev/NCronJob/pull/215).

## [v4.3.4] - 2025-02-25

### Fixed
Expand Down
5 changes: 4 additions & 1 deletion docs/advanced/dynamic-job-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ To add a job at runtime, leverage the `IRuntimeJobRegistry` interface:
```csharp
app.MapPost("/add-job", (IRuntimeJobRegistry registry) =>
{
var hasSucceeded = registry.TryRegister(n => n.AddJob<SampleJob>(p => p.WithCronExpression("* * * * *").WithName("MyName")), out Exception? exc);
var hasSucceeded = registry.TryRegister(
n => n.AddJob<SampleJob>(
p => p.WithCronExpression("* * * * *").WithName("MyName")),
out Exception? exc);

if (!hasSucceeded)
{
Expand Down
30 changes: 28 additions & 2 deletions docs/features/instant-jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,32 @@ app.MapPost("/send-email", (RequestDto dto, IInstantJobRegistry jobRegistry) =>
});
```

## Triggering executions of named orchestrations

While registering jobs, one can give them names (See [*"Defining job names"*](../advanced/dynamic-job-control.md#defining-job-names).).

Similarly, the `IInstantJobRegistry` methods expose overloads to target a job through its name.

This capability provides an escape hatch to situations as described in [*"Precautions of use"*](#precautions-of-use) below.

```csharp
Services.AddNCronJob(options =>
{
options.AddJob<GatherDataJob>(s => s.WithCronExpression("0 0 * * *").WithName("nightly"))
.ExecuteWhen(success: s => s.RunJob<SendReportToStaffJob>());

options.AddJob<GatherDataJob>(s => s.WithCronExpression("0 0 1 * *").WithName("monthly"))
.ExecuteWhen(success: s => s.RunJob<SendReportToManagementJob>());
});

app.MapPost("/on-demand-report", (IInstantJobRegistry registry) => {

registry.RunInstantJob("nightly");

return Results.Accepted();
});
```

## Precautions of use

Depending of the way jobs are designed and registered, it may happen that **NCronJob** cannot uniquely identify the intended target of a instant run.
Expand All @@ -159,10 +185,10 @@ In that case, rather than accidently running the wrong job, a runtime exception
Services.AddNCronJob(options =>
{
options.AddJob<GatherDataJob>(s => s.WithCronExpression("0 0 * * *")) // Every day at midnight
.ExecuteWhen(success: s => s.RunJob<SendReportToStaffJob>());
.ExecuteWhen(success: s => s.RunJob<SendReportToStaffJob>());

options.AddJob<GatherDataJob>(s => s.WithCronExpression("0 0 1 * *")) // Every first of the month at midnight
.ExecuteWhen(success: s => s.RunJob<SendReportToManagementJob>());
.ExecuteWhen(success: s => s.RunJob<SendReportToManagementJob>());
});

app.MapPost("/on-demand-report", (IInstantJobRegistry registry) => {
Expand Down
13 changes: 7 additions & 6 deletions docs/features/model-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,10 @@ builder.Services.AddNCronJob(options => {
## Run mutliple jobs after the completion of a job

You can run multiple jobs after the completion of a job:
```no-class
Job A -- successful --> Job B
|- successful --> Job C

```text
Job A ─┬─ (successful) ──> Job B
└─ (successful) ──> Job C
```

Can be achieved in two ways:
Expand Down Expand Up @@ -168,10 +169,10 @@ Services.AddNCronJob(options =>
If you need to ensure the order of execution, see the next section.

## Construct complex dependencies
You can construct complex dependencies by using a combination of `AddJob` and `ExecuteWhen` methods. Here an example:
You can construct complex dependencies by using a combination of `AddJob` and `ExecuteWhen` methods. Below an example:

```
Job A -> Job B -> Job C
```text
Job A ──> Job B ──> Job C
```

This can be achieved by:
Expand Down
12 changes: 12 additions & 0 deletions src/NCronJob/Configuration/Builder/IOptionChainerBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace NCronJob;

/// <summary>
/// Expose a way to chain different options.
/// </summary>
public interface IOptionChainerBuilder
{
/// <summary>
/// Chains another option to the job.
/// </summary>
JobOptionBuilder And { get; }
}
20 changes: 19 additions & 1 deletion src/NCronJob/Configuration/Builder/JobOptionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public sealed class JobOptionBuilder
/// </summary>
/// <param name="cronExpression">The cron expression that defines when the job should be executed.</param>
/// <param name="timeZoneInfo">Optional, provides the timezone that is used to evaluate the cron expression. Defaults to UTC.</param>
/// <returns>Returns a <see cref="ParameterBuilder"/> that allows adding parameters to the job.</returns>
/// <returns>Returns a <see cref="ParameterBuilder"/> that allows naming the job or adding a parameter to it.</returns>
public ParameterBuilder WithCronExpression(string cronExpression, TimeZoneInfo? timeZoneInfo = null)
{
ArgumentNullException.ThrowIfNull(cronExpression);
Expand All @@ -30,6 +30,24 @@ public ParameterBuilder WithCronExpression(string cronExpression, TimeZoneInfo?
return new ParameterBuilder(this, jobOption);
}

/// <summary>
/// Sets the job name. This can be used to identify the job.
/// </summary>
/// <param name="jobName">The job name associated with this job.</param>
/// <returns>Returns a <see cref="ParameterBuilder"/> that allows further configuration.</returns>
/// <remarks>The job name should be unique over all job instances.</remarks>
public ParameterOnlyBuilder WithName(string jobName)
{
var jobOption = new JobOption
{
Name = jobName,
};

jobOptions.Add(jobOption);

return new ParameterOnlyBuilder(this, jobOption);
}

internal List<JobOption> GetJobOptions()
{
if (jobOptions.Count == 0)
Expand Down
6 changes: 3 additions & 3 deletions src/NCronJob/Configuration/Builder/ParameterBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
namespace NCronJob;

/// <summary>
/// Represents a builder to create parameters for a job.
/// Represents a builder to configure a job with a name or a parameter.
/// </summary>
public sealed class ParameterBuilder
public sealed class ParameterBuilder : IOptionChainerBuilder
{
private readonly JobOption jobOption;

/// <summary>
/// Chains another cron expression to the job.
/// Chains another option to the job.
/// </summary>
public JobOptionBuilder And { get; }

Expand Down
33 changes: 33 additions & 0 deletions src/NCronJob/Configuration/Builder/ParameterOnlyBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace NCronJob;

/// <summary>
/// Represents a builder to configure a job with a parameter.
/// </summary>
public sealed class ParameterOnlyBuilder : IOptionChainerBuilder
{
private readonly JobOptionBuilder optionBuilder;
private readonly JobOption jobOption;

internal ParameterOnlyBuilder(JobOptionBuilder optionBuilder, JobOption jobOption)
{
this.optionBuilder = optionBuilder;
this.jobOption = jobOption;
}

/// <summary>
/// Chains another option to the job.
/// </summary>
public JobOptionBuilder And => optionBuilder;

/// <summary>
/// The parameter that can be passed down to the job.<br/>
/// When an instant job is triggered a parameter can be passed down via the <see cref="IInstantJobRegistry"/> interface.
/// </summary>
/// <param name="parameter">The parameter to add that will be passed to the cron job.</param>
/// <returns>Returns a <see cref="IOptionChainerBuilder"/> that allows chaining new options.</returns>
public IOptionChainerBuilder WithParameter(object? parameter)
{
jobOption.Parameter = parameter;
return this;
}
}
2 changes: 1 addition & 1 deletion src/NCronJob/Configuration/DynamicJobFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public Task RunAsync(IJobExecutionContext context, CancellationToken token)
var arguments = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
if (parameters[i].ParameterType == typeof(JobExecutionContext))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As JobExecutionContext isn't a public type, dynamic jobs can't even declare it as parameter.
Switching this to IJobExecutionContext aligns with the what the doco advertizes

if (parameters[i].ParameterType == typeof(IJobExecutionContext))
arguments[i] = context;
else if (parameters[i].ParameterType == typeof(CancellationToken))
arguments[i] = token;
Expand Down
Loading