Table of Contents

Class QuartzBuilderExtensions

Namespace
Quartz
Assembly
Quartz.dll

Adds jobs, triggers and calendars to a scheduler, and selects the built-in type loader.

public static class QuartzBuilderExtensions
Inheritance
QuartzBuilderExtensions
Inherited Members

Remarks

These extend IQuartzBuilder, so they read the same whether the scheduler is being registered with AddQuartz or built by QuartzSchedulerBuilder. They are extension methods rather than interface members because each is a composition of what the interface already offers, and an implementation of it should not have to reproduce them.

Methods

AddCalendar(IQuartzBuilder, string, ICalendar, AddCalendarOptions)

Adds a calendar the caller has already built.

public static IQuartzBuilder AddCalendar(this IQuartzBuilder builder, string name, ICalendar calendar, AddCalendarOptions options = default)

Parameters

builder IQuartzBuilder

The builder.

name string

The name triggers refer to the calendar by.

calendar ICalendar

The calendar.

options AddCalendarOptions

How the calendar is added: whether it replaces one of the same name, and whether triggers using that name are recomputed. Defaults to replacing nothing.

Returns

IQuartzBuilder

AddCalendar(IQuartzBuilder, string, Func<IServiceProvider, ICalendar>, AddCalendarOptions)

Adds a calendar built from the container.

public static IQuartzBuilder AddCalendar(this IQuartzBuilder builder, string name, Func<IServiceProvider, ICalendar> factory, AddCalendarOptions options = default)

Parameters

builder IQuartzBuilder

The builder.

name string

The name triggers refer to the calendar by.

factory Func<IServiceProvider, ICalendar>

Builds the calendar from the container.

options AddCalendarOptions

How the calendar is added: whether it replaces one of the same name, and whether triggers using that name are recomputed. Defaults to replacing nothing.

Returns

IQuartzBuilder

Remarks

The generic forms above create the calendar with new T(), so a calendar that needs a dependency — a holiday list read from a database, a clock, an options snapshot — cannot use them. This one hands the factory the scheduler-scoped IServiceProvider, so a calendar registered for a named scheduler is given that scheduler's parts. The factory runs once, when the scheduler's content is resolved.

AddCalendar<T>(IQuartzBuilder, string, AddCalendarOptions, Action<IServiceProvider, T>)

Adds a calendar the scheduler should carry, which triggers exclude days with.

public static IQuartzBuilder AddCalendar<T>(this IQuartzBuilder builder, string name, AddCalendarOptions options, Action<IServiceProvider, T> configure) where T : ICalendar, new()

Parameters

builder IQuartzBuilder

The builder.

name string

The name triggers refer to the calendar by.

options AddCalendarOptions

How the calendar is added: whether it replaces one of the same name, and whether triggers using that name are recomputed. Defaults to replacing nothing.

configure Action<IServiceProvider, T>

Configures the calendar, which is created with its default constructor.

Returns

IQuartzBuilder

Type Parameters

T

The calendar's type.

AddCalendar<T>(IQuartzBuilder, string, AddCalendarOptions, Action<T>?)

Adds a calendar the scheduler should carry, which triggers exclude days with.

public static IQuartzBuilder AddCalendar<T>(this IQuartzBuilder builder, string name, AddCalendarOptions options = default, Action<T>? configure = null) where T : ICalendar, new()

Parameters

builder IQuartzBuilder

The builder.

name string

The name triggers refer to the calendar by.

options AddCalendarOptions

How the calendar is added: whether it replaces one of the same name, and whether triggers using that name are recomputed. Defaults to replacing nothing.

configure Action<T>

Configures the calendar, which is created with its default constructor.

Returns

IQuartzBuilder

Type Parameters

T

The calendar's type.

AddJob(IQuartzBuilder, string, Delegate, Action<IJobConfigurator<IJob>>?)

Adds a job whose code is a delegate, and whose parameters are what that code needs.

public static IQuartzBuilder AddJob(this IQuartzBuilder builder, string name, Delegate handler, Action<IJobConfigurator<IJob>>? configure = null)

Parameters

builder IQuartzBuilder

The builder.

name string

The job's name, which makes its key in the default group unless configure gives it another identity.

handler Delegate

The job's code.

configure Action<IJobConfigurator<IJob>>

Configures the job, which most jobs do not need to.

Returns

IQuartzBuilder

Remarks

Each parameter is bound when the job is added: IJobExecutionContext is the firing, CancellationToken is the firing's token, IServiceProvider is the firing's scope, and any other type is a required service resolved from that scope. The handler returns ValueTask, Task or nothing. Its parameters need their types written out: a lambda without them has no delegate type to be passed as.

A parameter whose type belongs to one scheduler — IScheduler, ISchedulerFactory, a scheduler's options — fails validation at startup, as it does in a registered job's constructor. Read the scheduler running the job from Scheduler instead.

The job is durable, so it stays stored with no trigger; it fires when a trigger of its own names it, or through TriggerJob(JobKey, JobDataMap?, CancellationToken). It is stored as Quartz.Impl.DelegateJob, and its key is what a firing finds the handler by, so the handler has to be registered on every node that runs the scheduler.

Exceptions

ArgumentException

name is empty, or handler cannot be run as a job: it returns something other than ValueTask, Task or nothing, is async void, takes a ref, out, in, pointer or ref struct parameter, or combines several delegates.

AddJob(IQuartzBuilder, string, Delegate, Action<IServiceProvider, IJobConfigurator<IJob>>)

Adds a job whose code is a delegate, and whose parameters are what that code needs.

public static IQuartzBuilder AddJob(this IQuartzBuilder builder, string name, Delegate handler, Action<IServiceProvider, IJobConfigurator<IJob>> configure)

Parameters

builder IQuartzBuilder

The builder.

name string

The job's name, which makes its key in the default group unless configure gives it another identity.

handler Delegate

The job's code.

configure Action<IServiceProvider, IJobConfigurator<IJob>>

Configures the job, given this scheduler's view of the container.

Returns

IQuartzBuilder

Remarks

Each parameter is bound when the job is added: IJobExecutionContext is the firing, CancellationToken is the firing's token, IServiceProvider is the firing's scope, and any other type is a required service resolved from that scope. The handler returns ValueTask, Task or nothing. Its parameters need their types written out: a lambda without them has no delegate type to be passed as.

A parameter whose type belongs to one scheduler — IScheduler, ISchedulerFactory, a scheduler's options — fails validation at startup, as it does in a registered job's constructor. Read the scheduler running the job from Scheduler instead.

The job is durable, so it stays stored with no trigger; it fires when a trigger of its own names it, or through TriggerJob(JobKey, JobDataMap?, CancellationToken). It is stored as Quartz.Impl.DelegateJob, and its key is what a firing finds the handler by, so the handler has to be registered on every node that runs the scheduler.

Exceptions

ArgumentException

name is empty, or handler cannot be run as a job: it returns something other than ValueTask, Task or nothing, is async void, takes a ref, out, in, pointer or ref struct parameter, or combines several delegates.

AddJob(IQuartzBuilder, Type, Action<IJobConfigurator<IJob>>)

Adds a job of a type only known at runtime.

public static IQuartzBuilder AddJob(this IQuartzBuilder builder, Type jobType, Action<IJobConfigurator<IJob>> configure)

Parameters

builder IQuartzBuilder

The builder.

jobType Type

The job's type, which must implement IJob.

configure Action<IJobConfigurator<IJob>>

Configures the job. Its identity is set here with WithIdentity; a job given none gets a generated one, which a persistent store cannot recognise again on the next start.

Returns

IQuartzBuilder

Remarks

Registers the job type with the container, so a dependency it cannot be given is reported when the container is validated rather than when the trigger fires.

The registration is scoped, matching the lifetime the job factory resolves with: a scope is opened per fire, the job is resolved from it, and the scope is disposed once the job returns. A singleton would serve every fire from one instance and capture the scoped dependencies handed to the first one; a transient would leave two resolutions inside one fire — the job and something it injects — disagreeing about which unit of work they are in.

It is a TryAdd, so a registration the application made itself — with its own lifetime, factory or implementation type — is kept, and adding the same job twice is harmless.

It is made only for a scheduler whose job factory builds jobs from the container: MicrosoftDependencyInjectionJobFactory, the default, or a factory derived from it. A scheduler given a job factory of its own with UseJobFactory registers nothing, because its factory builds the job its own way and container validation would fail the job on dependencies only that factory supplies. The factory is read once the scheduler's registration is complete, so UseJobFactory may come before or after this call.

Being registered is also what makes the job the container's to build rather than the job factory's to activate, so the scheduler is recorded as having been given it and Quartz.Configuration.RegisteredJobConstructorValidator checks at startup that its constructor asks for nothing that belongs to one scheduler.

AddJob(IQuartzBuilder, Type, Action<IServiceProvider, IJobConfigurator<IJob>>)

Adds a job of a type only known at runtime.

public static IQuartzBuilder AddJob(this IQuartzBuilder builder, Type jobType, Action<IServiceProvider, IJobConfigurator<IJob>> configure)

Parameters

builder IQuartzBuilder

The builder.

jobType Type

The job's type, which must implement IJob.

configure Action<IServiceProvider, IJobConfigurator<IJob>>

Configures the job. Its identity is set here with WithIdentity; a job given none gets a generated one, which a persistent store cannot recognise again on the next start.

Returns

IQuartzBuilder

Remarks

Registers the job type with the container, so a dependency it cannot be given is reported when the container is validated rather than when the trigger fires.

The registration is scoped, matching the lifetime the job factory resolves with: a scope is opened per fire, the job is resolved from it, and the scope is disposed once the job returns. A singleton would serve every fire from one instance and capture the scoped dependencies handed to the first one; a transient would leave two resolutions inside one fire — the job and something it injects — disagreeing about which unit of work they are in.

It is a TryAdd, so a registration the application made itself — with its own lifetime, factory or implementation type — is kept, and adding the same job twice is harmless.

It is made only for a scheduler whose job factory builds jobs from the container: MicrosoftDependencyInjectionJobFactory, the default, or a factory derived from it. A scheduler given a job factory of its own with UseJobFactory registers nothing, because its factory builds the job its own way and container validation would fail the job on dependencies only that factory supplies. The factory is read once the scheduler's registration is complete, so UseJobFactory may come before or after this call.

Being registered is also what makes the job the container's to build rather than the job factory's to activate, so the scheduler is recorded as having been given it and Quartz.Configuration.RegisteredJobConstructorValidator checks at startup that its constructor asks for nothing that belongs to one scheduler.

AddJobLogScope(IQuartzBuilder)

Opens a logging scope around every firing that names its job, its trigger and its fire instance, so each line the job logs says which firing wrote it.

public static IQuartzBuilder AddJobLogScope(this IQuartzBuilder builder)

Parameters

builder IQuartzBuilder

The builder.

Returns

IQuartzBuilder

Remarks

The scope carries quartz.job.name, quartz.job.group, quartz.trigger.name, quartz.trigger.group and quartz.fire.instance.id: the ActivityTags names the execution span carries, so a log line and the span of its firing are found by the same values. A provider shows a scope only when it includes scopes, as IncludeScopes does on the console formatter and on OpenTelemetry's logger.

Opt-in because it is not free: a scope is an AsyncLocal write, which copies the execution context once per firing. The scope naming the scheduler is opened once per scheduling loop instead, and costs a firing nothing.

A middleware, registered where the call is written: called first, the scope is outside every other middleware, so what they log carries it too. The job and trigger listeners and the run shell's own lines about the firing are outside the pipeline, and do not. Calling it twice for one scheduler opens one scope, in the place of the first call.

AddJobTimeout(IQuartzBuilder, Func<IServiceProvider, TimeSpan?>)

Bounds how long a firing may run: when its budget is spent the execution is interrupted, and the overrun is reported as the job's own failure.

public static IQuartzBuilder AddJobTimeout(this IQuartzBuilder builder, Func<IServiceProvider, TimeSpan?> defaultTimeout)

Parameters

builder IQuartzBuilder

The builder.

defaultTimeout Func<IServiceProvider, TimeSpan?>

Answers how long a job that carries no JobTimeoutAttribute may run, given this scheduler's view of the container. null bounds only the jobs that declare a budget of their own.

Returns

IQuartzBuilder

Remarks

Where a budget is found, what a timeout does to the trigger and why a job that ignores its token cannot be stopped are all as AddJobTimeout(IQuartzBuilder, TimeSpan?) describes.

What differs is when the budget is read: here it is read as the scheduler is built rather than as the call is written, so it can come from a service — options the application configured, most often. Everything Configure, PostConfigure and IValidateOptions contribute has run by the time IOptions<T> answers here, which reading configuration at registration time cannot say. Refusing a budget moves with it: a zero or negative one is reported when the scheduler is built, not when this is called.

Exceptions

ArgumentOutOfRangeException

defaultTimeout answered zero or negative. A scheduler-wide default of nothing is what answering null already says.

AddJobTimeout(IQuartzBuilder, TimeSpan?)

Bounds how long a firing may run: when its budget is spent the execution is interrupted, and the overrun is reported as the job's own failure.

public static IQuartzBuilder AddJobTimeout(this IQuartzBuilder builder, TimeSpan? defaultTimeout = null)

Parameters

builder IQuartzBuilder

The builder.

defaultTimeout TimeSpan?

How long a job that carries no JobTimeoutAttribute may run. null, the default, bounds only the jobs that declare a budget of their own.

Returns

IQuartzBuilder

Remarks

A middleware, registered where the call is written, so it takes its place in the chain like any other — outside a log scope registered after it, inside one registered before. It is an extension rather than a member of IQuartzBuilder for the reason everything else here is: it is AddJobMiddleware with the built-in middleware, and an implementation of the interface should not have to reproduce it.

How the budget is found. The job type's JobTimeoutAttribute decides, and defaultTimeout applies to every job that carries none. A job declaring [JobTimeout("00:00:00")] has no timeout at all, so a long-running job is exempt from a scheduler-wide default rather than fighting it. Called with no argument, only the jobs that declare a budget are bounded.

What a timeout does. The firing is interrupted through InterruptFireInstance(string, CancellationToken), exactly as an operator's interrupt would, so the job's CancellationToken — the one it was handed — is cancelled and JobInterrupted(IScheduler, JobKey, string, CancellationToken) is raised. The middleware then raises a JobExecutionException naming the budget, which is what makes a timeout a failure: without it an interrupt looks like a completed job, and the firing's RetryPolicy would never be consulted. With it, a timed-out firing is retried like any other failure, and each attempt gets the whole budget again.

A job that ignores its token cannot be stopped. Cancellation is cooperative; nothing here aborts a thread. A job that never looks at its token runs to completion and is reported as timed out when it finally returns — which is worth knowing before a budget is relied on to free a thread-pool slot. CA2016 is what polices forwarding the token.

Exceptions

ArgumentOutOfRangeException

defaultTimeout is zero or negative. A scheduler-wide default of nothing is what leaving it out already says.

AddJobType<TJob>(IQuartzBuilder)

Registers how this scheduler builds a job type, so two schedulers in one container can build the same job type differently.

public static IQuartzBuilder AddJobType<TJob>(this IQuartzBuilder builder) where TJob : class, IJob

Parameters

builder IQuartzBuilder

The builder.

Returns

IQuartzBuilder

Type Parameters

TJob

The job type, as named on the job detail.

Remarks

AddJob<T>(IQuartzBuilder, Action<IJobConfigurator<T>>) registers the job type with the container unkeyed, which is what a single-scheduler application wants and what container validation reads. Under a scheduler per tenant that one registration is shared: whichever implementation or lifetime was registered first is what every scheduler gets. This says the registration belongs to one scheduler, and the job factory looks there first.

The registration is made under this scheduler's service key, or unkeyed for the default scheduler — whose registrations are the unkeyed ones, so an empty key would not be the same thing. It replaces rather than defers to what AddJob registered: naming the implementation is the whole point of the call.

A job is built with Scoped unless another lifetime is named, which matches the lifetime the job factory resolves with: a scope is opened per fire, the job is resolved from it, and the scope is disposed once the job returns.

The lifetime is an overload rather than an optional parameter deliberately. A default value that is an enum from an assembly which only exists in a shared framework — ServiceLifetime is one — is a metadata constant whose type Cecil has to resolve to write it, and coverlet's resolver cannot: it fails to instrument the whole assembly and reports Quartz as untested, silently. One optional parameter cost the core assembly its entire coverage figure; overloads carry no constant.

AddJobType<TJob>(IQuartzBuilder, ServiceLifetime)

Registers how this scheduler builds a job type, so two schedulers in one container can build the same job type differently.

public static IQuartzBuilder AddJobType<TJob>(this IQuartzBuilder builder, ServiceLifetime lifetime) where TJob : class, IJob

Parameters

builder IQuartzBuilder

The builder.

lifetime ServiceLifetime

How long one instance lives.

Returns

IQuartzBuilder

Type Parameters

TJob

AddJobType<TJob>(IQuartzBuilder, Func<IServiceProvider, TJob>)

Registers how this scheduler constructs a job type, with a factory of your own.

public static IQuartzBuilder AddJobType<TJob>(this IQuartzBuilder builder, Func<IServiceProvider, TJob> implementationFactory) where TJob : class, IJob

Parameters

builder IQuartzBuilder

The builder.

implementationFactory Func<IServiceProvider, TJob>

Builds the job.

Returns

IQuartzBuilder

Type Parameters

TJob

The job type, as named on the job detail.

Remarks

AddJob<T>(IQuartzBuilder, Action<IJobConfigurator<T>>) registers the job type with the container unkeyed, which is what a single-scheduler application wants and what container validation reads. Under a scheduler per tenant that one registration is shared: whichever implementation or lifetime was registered first is what every scheduler gets. This says the registration belongs to one scheduler, and the job factory looks there first.

The registration is made under this scheduler's service key, or unkeyed for the default scheduler — whose registrations are the unkeyed ones, so an empty key would not be the same thing. It replaces rather than defers to what AddJob registered: naming the implementation is the whole point of the call.

A job is built with Scoped unless another lifetime is named, which matches the lifetime the job factory resolves with: a scope is opened per fire, the job is resolved from it, and the scope is disposed once the job returns.

The lifetime is an overload rather than an optional parameter deliberately. A default value that is an enum from an assembly which only exists in a shared framework — ServiceLifetime is one — is a metadata constant whose type Cecil has to resolve to write it, and coverlet's resolver cannot: it fails to instrument the whole assembly and reports Quartz as untested, silently. One optional parameter cost the core assembly its entire coverage figure; overloads carry no constant.

AddJobType<TJob>(IQuartzBuilder, Func<IServiceProvider, TJob>, ServiceLifetime)

Registers how this scheduler constructs a job type, with a factory of your own.

public static IQuartzBuilder AddJobType<TJob>(this IQuartzBuilder builder, Func<IServiceProvider, TJob> implementationFactory, ServiceLifetime lifetime) where TJob : class, IJob

Parameters

builder IQuartzBuilder

The builder.

implementationFactory Func<IServiceProvider, TJob>

Builds the job.

lifetime ServiceLifetime

How long one instance lives.

Returns

IQuartzBuilder

Type Parameters

TJob

AddJobType<TJob, TImplementation>(IQuartzBuilder)

Registers the implementation this scheduler builds a job type with.

public static IQuartzBuilder AddJobType<TJob, TImplementation>(this IQuartzBuilder builder) where TJob : class, IJob where TImplementation : class, TJob

Parameters

builder IQuartzBuilder

The builder.

Returns

IQuartzBuilder

Type Parameters

TJob

The job type, as named on the job detail.

TImplementation

The type actually constructed.

Remarks

AddJob<T>(IQuartzBuilder, Action<IJobConfigurator<T>>) registers the job type with the container unkeyed, which is what a single-scheduler application wants and what container validation reads. Under a scheduler per tenant that one registration is shared: whichever implementation or lifetime was registered first is what every scheduler gets. This says the registration belongs to one scheduler, and the job factory looks there first.

The registration is made under this scheduler's service key, or unkeyed for the default scheduler — whose registrations are the unkeyed ones, so an empty key would not be the same thing. It replaces rather than defers to what AddJob registered: naming the implementation is the whole point of the call.

A job is built with Scoped unless another lifetime is named, which matches the lifetime the job factory resolves with: a scope is opened per fire, the job is resolved from it, and the scope is disposed once the job returns.

The lifetime is an overload rather than an optional parameter deliberately. A default value that is an enum from an assembly which only exists in a shared framework — ServiceLifetime is one — is a metadata constant whose type Cecil has to resolve to write it, and coverlet's resolver cannot: it fails to instrument the whole assembly and reports Quartz as untested, silently. One optional parameter cost the core assembly its entire coverage figure; overloads carry no constant.

AddJobType<TJob, TImplementation>(IQuartzBuilder, ServiceLifetime)

Registers the implementation this scheduler builds a job type with.

public static IQuartzBuilder AddJobType<TJob, TImplementation>(this IQuartzBuilder builder, ServiceLifetime lifetime) where TJob : class, IJob where TImplementation : class, TJob

Parameters

builder IQuartzBuilder

The builder.

lifetime ServiceLifetime

How long one instance lives.

Returns

IQuartzBuilder

Type Parameters

TJob
TImplementation

AddJob<T>(IQuartzBuilder, Action<IJobConfigurator<T>>)

Adds a job the scheduler should carry.

public static IQuartzBuilder AddJob<T>(this IQuartzBuilder builder, Action<IJobConfigurator<T>> configure) where T : IJob

Parameters

builder IQuartzBuilder

The builder.

configure Action<IJobConfigurator<T>>

Configures the job. Its identity is set here with WithIdentity; a job given none gets a generated one, which a persistent store cannot recognise again on the next start.

Returns

IQuartzBuilder

Type Parameters

T

The job's type.

Remarks

Registers the job type with the container, so a dependency it cannot be given is reported when the container is validated rather than when the trigger fires.

The registration is scoped, matching the lifetime the job factory resolves with: a scope is opened per fire, the job is resolved from it, and the scope is disposed once the job returns. A singleton would serve every fire from one instance and capture the scoped dependencies handed to the first one; a transient would leave two resolutions inside one fire — the job and something it injects — disagreeing about which unit of work they are in.

It is a TryAdd, so a registration the application made itself — with its own lifetime, factory or implementation type — is kept, and adding the same job twice is harmless.

It is made only for a scheduler whose job factory builds jobs from the container: MicrosoftDependencyInjectionJobFactory, the default, or a factory derived from it. A scheduler given a job factory of its own with UseJobFactory registers nothing, because its factory builds the job its own way and container validation would fail the job on dependencies only that factory supplies. The factory is read once the scheduler's registration is complete, so UseJobFactory may come before or after this call.

Being registered is also what makes the job the container's to build rather than the job factory's to activate, so the scheduler is recorded as having been given it and Quartz.Configuration.RegisteredJobConstructorValidator checks at startup that its constructor asks for nothing that belongs to one scheduler.

AddJob<T>(IQuartzBuilder, Action<IServiceProvider, IJobConfigurator<T>>)

Adds a job the scheduler should carry.

public static IQuartzBuilder AddJob<T>(this IQuartzBuilder builder, Action<IServiceProvider, IJobConfigurator<T>> configure) where T : IJob

Parameters

builder IQuartzBuilder

The builder.

configure Action<IServiceProvider, IJobConfigurator<T>>

Configures the job. Its identity is set here with WithIdentity; a job given none gets a generated one, which a persistent store cannot recognise again on the next start.

Returns

IQuartzBuilder

Type Parameters

T

The job's type.

Remarks

Registers the job type with the container, so a dependency it cannot be given is reported when the container is validated rather than when the trigger fires.

The registration is scoped, matching the lifetime the job factory resolves with: a scope is opened per fire, the job is resolved from it, and the scope is disposed once the job returns. A singleton would serve every fire from one instance and capture the scoped dependencies handed to the first one; a transient would leave two resolutions inside one fire — the job and something it injects — disagreeing about which unit of work they are in.

It is a TryAdd, so a registration the application made itself — with its own lifetime, factory or implementation type — is kept, and adding the same job twice is harmless.

It is made only for a scheduler whose job factory builds jobs from the container: MicrosoftDependencyInjectionJobFactory, the default, or a factory derived from it. A scheduler given a job factory of its own with UseJobFactory registers nothing, because its factory builds the job its own way and container validation would fail the job on dependencies only that factory supplies. The factory is read once the scheduler's registration is complete, so UseJobFactory may come before or after this call.

Being registered is also what makes the job the container's to build rather than the job factory's to activate, so the scheduler is recorded as having been given it and Quartz.Configuration.RegisteredJobConstructorValidator checks at startup that its constructor asks for nothing that belongs to one scheduler.

AddTrigger(IQuartzBuilder, Action<ITriggerConfigurator<IJob>>)

Adds a trigger for a job added elsewhere, named by key.

public static IQuartzBuilder AddTrigger(this IQuartzBuilder builder, Action<ITriggerConfigurator<IJob>> configure)

Parameters

builder IQuartzBuilder

The builder.

configure Action<ITriggerConfigurator<IJob>>

Configures the trigger, which must name its job with ForJob.

Returns

IQuartzBuilder

Remarks

The job type on AddTrigger<TJob>(IQuartzBuilder, Action<ITriggerConfigurator<TJob>>) is what lets a trigger's job data name the job's properties; a trigger that only points at a job with ForJob has no use for it, and this is that call without the <IJob>.

AddTrigger(IQuartzBuilder, Action<IServiceProvider, ITriggerConfigurator<IJob>>)

Adds a trigger for a job added elsewhere, named by key.

public static IQuartzBuilder AddTrigger(this IQuartzBuilder builder, Action<IServiceProvider, ITriggerConfigurator<IJob>> configure)

Parameters

builder IQuartzBuilder

The builder.

configure Action<IServiceProvider, ITriggerConfigurator<IJob>>

Configures the trigger, which must name its job with ForJob.

Returns

IQuartzBuilder

Remarks

The job type on AddTrigger<TJob>(IQuartzBuilder, Action<ITriggerConfigurator<TJob>>) is what lets a trigger's job data name the job's properties; a trigger that only points at a job with ForJob has no use for it, and this is that call without the <IJob>.

AddTrigger<TJob>(IQuartzBuilder, Action<ITriggerConfigurator<TJob>>)

Adds a trigger for a job of a known type.

public static IQuartzBuilder AddTrigger<TJob>(this IQuartzBuilder builder, Action<ITriggerConfigurator<TJob>> configure) where TJob : IJob

Parameters

builder IQuartzBuilder

The builder.

configure Action<ITriggerConfigurator<TJob>>

Configures the trigger.

Returns

IQuartzBuilder

Type Parameters

TJob

The type of job the trigger fires.

Remarks

Naming the job type is what lets the trigger's job data name the job's properties. The trigger still has to be pointed at a job with ForJob, and since that is done by key here, nothing checks that the key resolves to a TJob - the type names the properties, it does not pick the job. Use AddTrigger<IJob> for a trigger whose job data names nothing.

AddTrigger<TJob>(IQuartzBuilder, Action<IServiceProvider, ITriggerConfigurator<TJob>>)

Adds a trigger for a job of a known type.

public static IQuartzBuilder AddTrigger<TJob>(this IQuartzBuilder builder, Action<IServiceProvider, ITriggerConfigurator<TJob>> configure) where TJob : IJob

Parameters

builder IQuartzBuilder

The builder.

configure Action<IServiceProvider, ITriggerConfigurator<TJob>>

Configures the trigger.

Returns

IQuartzBuilder

Type Parameters

TJob

The type of job the trigger fires.

Remarks

Naming the job type is what lets the trigger's job data name the job's properties. The trigger still has to be pointed at a job with ForJob, and since that is done by key here, nothing checks that the key resolves to a TJob - the type names the properties, it does not pick the job. Use AddTrigger<IJob> for a trigger whose job data names nothing.

ConfigureJobScope(IQuartzBuilder, Action<IServiceScope, TriggerFiredBundle, IScheduler>)

Prepares the dependency injection scope each job is built in, so services that are scoped can be given the ambient context of the job about to run.

public static IQuartzBuilder ConfigureJobScope(this IQuartzBuilder builder, Action<IServiceScope, TriggerFiredBundle, IScheduler> configure)

Parameters

builder IQuartzBuilder

The builder.

configure Action<IServiceScope, TriggerFiredBundle, IScheduler>

Prepares the scope, given the scope, the fire it was opened for and the scheduler firing it.

Returns

IQuartzBuilder

Remarks

The callback runs before the job is resolved, so anything it sets is in place while the job and everything it injects are constructed. It is synchronous by design: an asynchronous hook would be awaited, and the ExecutionContext restored on the way back would discard exactly the AsyncLocal<T> values it exists to set.

Callbacks combine rather than replace, and run in the order they were added. This was previously reachable only by deriving from MicrosoftDependencyInjectionJobFactory and overriding a protected method, which is a great deal of ceremony for setting one ambient value.

TriggerFiredBundle comes from Quartz.Extensibility, which is the one place on this builder where a mainstream caller meets that namespace without supplying a component. It is ratified rather than papered over: the callback runs before the job exists, and IJobExecutionContext — the type this would otherwise hand over — is constructed around a job instance. The bundle is what a firing is before there is a job to put in a context, so a mainstream stand-in would be a second bundle carrying the same four values.

PauseTriggerWhenRetriesExhausted(IQuartzBuilder)

Pauses a trigger whose retry policy has given up, recording why, so it stops failing on schedule until an operator resumes it.

public static IQuartzBuilder PauseTriggerWhenRetriesExhausted(this IQuartzBuilder builder)

Parameters

builder IQuartzBuilder

The builder.

Returns

IQuartzBuilder

Remarks

Registers a trigger listener that answers TriggerRetriesExhausted(ITrigger, IJobExecutionContext, JobExecutionException, CancellationToken) with PauseTriggerWith(TriggerKey, PauseDetails?, CancellationToken): the reason is the message of what the job threw, cut to MaxReasonLength, and the requester quartz:retries-exhausted. GetTriggerPause(TriggerKey, CancellationToken) and the dashboard show both. Only a firing that a retry policy applies to — the trigger's, its job type's or the scheduler's default — can exhaust its retries, so only those triggers are ever paused.

A trigger with no next occurrence is finished by the same completion, and there is nothing left of it to pause. Calling this twice for one scheduler registers one listener.

RefuseExecutionGroups(IQuartzBuilder, params string[])

Makes this node never acquire or misfire-handle the execution groups named, and serve every other.

public static IQuartzBuilder RefuseExecutionGroups(this IQuartzBuilder builder, params string[] groups)

Parameters

builder IQuartzBuilder

The builder.

groups string[]

The execution groups this node refuses.

Returns

IQuartzBuilder

Remarks

Sugar over RefusedExecutionGroups, with the spellings and the rules of ServeExecutionGroups(IQuartzBuilder, params string[]). A node that refuses every group in use is an administration node: it runs nothing and serves the dashboard.

RunAtStartup(IQuartzBuilder, JobKey)

Runs a job once each time this scheduler starts, beside whatever triggers it has.

public static IQuartzBuilder RunAtStartup(this IQuartzBuilder builder, JobKey jobKey)

Parameters

builder IQuartzBuilder

The builder.

jobKey JobKey

The job to run.

Returns

IQuartzBuilder

Remarks

On its first start — not when it is built, and not when it leaves standby — the scheduler schedules a one-shot trigger for the job, due at once, in StartupGroup under a name new to that start. A scheduler built in standby runs it when it is started; a restart runs it again. It is deleted once it has fired.

It is an ordinary trigger, so DisallowConcurrentExecutionAttribute, the job's listeners and the execution history apply to it as to any other. A persistent store keeps it until it has fired, so a firing a crash interrupts is recovered as any firing is. A run a crash prevented is replaced by the next start's run of the same node rather than added to it; one already reserved or running is left to finish.

In a cluster it runs once per node that starts: each node's trigger is pinned to that node with For(string), and fails over to another node only while that one is down.

The job has to be stored by then — durable, or with a trigger of its own — or the start fails with SchedulerException. Naming the job again registers nothing more: one run at each start. This is what a cron @reboot would have meant, which a cron expression cannot express: it has no instant to fire at.

ScheduleJob(IQuartzBuilder, string, Delegate, Action<ITriggerConfigurator<IJob>>)

Adds a job whose code is a delegate, together with the one trigger that fires it.

public static IQuartzBuilder ScheduleJob(this IQuartzBuilder builder, string name, Delegate handler, Action<ITriggerConfigurator<IJob>> trigger)

Parameters

builder IQuartzBuilder

The builder.

name string

The trigger's name, and so the job's, unless the trigger is given another.

handler Delegate

The job's code.

trigger Action<ITriggerConfigurator<IJob>>

Configures the trigger.

Returns

IQuartzBuilder

Remarks

The trigger is named name unless trigger gives it another identity, and the job takes the trigger's identity, as it does with ScheduleJob<T>(IQuartzBuilder, Action<ITriggerConfigurator<T>>, Action<IJobConfigurator<T>>?).

Parameters bind as they do for AddJob(IQuartzBuilder, string, Delegate, Action<IJobConfigurator<IJob>>?): the firing, its token, its scope, and required services from that scope. The job is stored as Quartz.Impl.DelegateJob, and its key is what a firing finds the handler by, so the handler has to be registered on every node that runs the scheduler.

Exceptions

ArgumentException

name is empty, or handler cannot be run as a job: it returns something other than ValueTask, Task or nothing, is async void, takes a ref, out, in, pointer or ref struct parameter, or combines several delegates.

ScheduleJob(IQuartzBuilder, string, Delegate, Action<IServiceProvider, ITriggerConfigurator<IJob>>)

Adds a job whose code is a delegate, together with the one trigger that fires it.

public static IQuartzBuilder ScheduleJob(this IQuartzBuilder builder, string name, Delegate handler, Action<IServiceProvider, ITriggerConfigurator<IJob>> trigger)

Parameters

builder IQuartzBuilder

The builder.

name string

The trigger's name, and so the job's, unless the trigger is given another.

handler Delegate

The job's code.

trigger Action<IServiceProvider, ITriggerConfigurator<IJob>>

Configures the trigger, given this scheduler's view of the container.

Returns

IQuartzBuilder

Remarks

The trigger is named name unless trigger gives it another identity, and the job takes the trigger's identity, as it does with ScheduleJob<T>(IQuartzBuilder, Action<ITriggerConfigurator<T>>, Action<IJobConfigurator<T>>?).

Parameters bind as they do for AddJob(IQuartzBuilder, string, Delegate, Action<IJobConfigurator<IJob>>?): the firing, its token, its scope, and required services from that scope. The job is stored as Quartz.Impl.DelegateJob, and its key is what a firing finds the handler by, so the handler has to be registered on every node that runs the scheduler.

Exceptions

ArgumentException

name is empty, or handler cannot be run as a job: it returns something other than ValueTask, Task or nothing, is async void, takes a ref, out, in, pointer or ref struct parameter, or combines several delegates.

ScheduleJob<T>(IQuartzBuilder, Action<ITriggerConfigurator<T>>, Action<IJobConfigurator<T>>?)

Adds a job together with the one trigger that fires it.

public static IQuartzBuilder ScheduleJob<T>(this IQuartzBuilder builder, Action<ITriggerConfigurator<T>> trigger, Action<IJobConfigurator<T>>? job = null) where T : IJob

Parameters

builder IQuartzBuilder

The builder.

trigger Action<ITriggerConfigurator<T>>

Configures the trigger.

job Action<IJobConfigurator<T>>

Configures the job, which most schedules do not need to.

Returns

IQuartzBuilder

Type Parameters

T

The job's type.

Remarks

The job takes the trigger's identity unless it is given one of its own, so a job and its only trigger can be referred to by a single name.

Registers the job type with the container, so a dependency it cannot be given is reported when the container is validated rather than when the trigger fires.

The registration is scoped, matching the lifetime the job factory resolves with: a scope is opened per fire, the job is resolved from it, and the scope is disposed once the job returns. A singleton would serve every fire from one instance and capture the scoped dependencies handed to the first one; a transient would leave two resolutions inside one fire — the job and something it injects — disagreeing about which unit of work they are in.

It is a TryAdd, so a registration the application made itself — with its own lifetime, factory or implementation type — is kept, and adding the same job twice is harmless.

It is made only for a scheduler whose job factory builds jobs from the container: MicrosoftDependencyInjectionJobFactory, the default, or a factory derived from it. A scheduler given a job factory of its own with UseJobFactory registers nothing, because its factory builds the job its own way and container validation would fail the job on dependencies only that factory supplies. The factory is read once the scheduler's registration is complete, so UseJobFactory may come before or after this call.

Being registered is also what makes the job the container's to build rather than the job factory's to activate, so the scheduler is recorded as having been given it and Quartz.Configuration.RegisteredJobConstructorValidator checks at startup that its constructor asks for nothing that belongs to one scheduler.

ScheduleJob<T>(IQuartzBuilder, Action<IServiceProvider, ITriggerConfigurator<T>>, Action<IServiceProvider, IJobConfigurator<T>>?)

Adds a job together with the one trigger that fires it.

public static IQuartzBuilder ScheduleJob<T>(this IQuartzBuilder builder, Action<IServiceProvider, ITriggerConfigurator<T>> trigger, Action<IServiceProvider, IJobConfigurator<T>>? job = null) where T : IJob

Parameters

builder IQuartzBuilder

The builder.

trigger Action<IServiceProvider, ITriggerConfigurator<T>>

Configures the trigger.

job Action<IServiceProvider, IJobConfigurator<T>>

Configures the job, which most schedules do not need to.

Returns

IQuartzBuilder

Type Parameters

T

The job's type.

Remarks

The job takes the trigger's identity unless it is given one of its own, so a job and its only trigger can be referred to by a single name.

Registers the job type with the container, so a dependency it cannot be given is reported when the container is validated rather than when the trigger fires.

The registration is scoped, matching the lifetime the job factory resolves with: a scope is opened per fire, the job is resolved from it, and the scope is disposed once the job returns. A singleton would serve every fire from one instance and capture the scoped dependencies handed to the first one; a transient would leave two resolutions inside one fire — the job and something it injects — disagreeing about which unit of work they are in.

It is a TryAdd, so a registration the application made itself — with its own lifetime, factory or implementation type — is kept, and adding the same job twice is harmless.

It is made only for a scheduler whose job factory builds jobs from the container: MicrosoftDependencyInjectionJobFactory, the default, or a factory derived from it. A scheduler given a job factory of its own with UseJobFactory registers nothing, because its factory builds the job its own way and container validation would fail the job on dependencies only that factory supplies. The factory is read once the scheduler's registration is complete, so UseJobFactory may come before or after this call.

Being registered is also what makes the job the container's to build rather than the job factory's to activate, so the scheduler is recorded as having been given it and Quartz.Configuration.RegisteredJobConstructorValidator checks at startup that its constructor asks for nothing that belongs to one scheduler.

ServeExecutionGroups(IQuartzBuilder, params string[])

Makes this node acquire and misfire-handle only the execution groups named, and nothing else.

public static IQuartzBuilder ServeExecutionGroups(this IQuartzBuilder builder, params string[] groups)

Parameters

builder IQuartzBuilder

The builder.

groups string[]

The execution groups this node serves.

Returns

IQuartzBuilder

Remarks

Sugar over ServedExecutionGroups: a group name, or _ for the triggers that carry none. A trigger in a group no live node serves waits until a serving node arrives; the health check and the dashboard report such a group. Calling this again adds to the list; naming a group twice is refused at startup, as is naming any group here and in RefuseExecutionGroups(IQuartzBuilder, params string[]) both.

A route, not a limit: how many of a served group run at once is still UseExecutionLimits(Action<ExecutionLimitsBuilder>)' business.

UseDefaultRetryPolicy(IQuartzBuilder, RetryPolicy)

Retries a failed firing under policy when neither its trigger nor its job type names a policy of its own.

public static IQuartzBuilder UseDefaultRetryPolicy(this IQuartzBuilder builder, RetryPolicy policy)

Parameters

builder IQuartzBuilder

The builder.

policy RetryPolicy

The policy, for example RetryPolicy.Exponential(5, TimeSpan.FromSeconds(30)).

Returns

IQuartzBuilder

Remarks

The last of three places a policy is looked for: the trigger's own RetryPolicy, then the job type's RetryPolicyAttribute, then this. A trigger given None, or a job type declaring [RetryPolicy(0)], is never retried whatever the default says.

Looked up when a firing fails, not written to triggers as they are stored, so it covers triggers stored before it was set, and a trigger's RETRY_POLICY column stays its own. Each node of a cluster applies its own default, so set the same one on every node; a node older than 4.4 has none.

Set in code only, because a RetryPolicy is built by its factories. Calling this again replaces the default.

UseDefaultTimeZone(IQuartzBuilder, TimeZoneInfo)

Gives every cron, calendar-interval, daily time-interval and recurrence trigger that names no time zone timeZone when it is scheduled, instead of Local.

public static IQuartzBuilder UseDefaultTimeZone(this IQuartzBuilder builder, TimeZoneInfo timeZone)

Parameters

builder IQuartzBuilder

The builder.

timeZone TimeZoneInfo

The zone, for example TimeZoneInfo.Utc or TimeZones.FindById("Europe/Helsinki").

Returns

IQuartzBuilder

Remarks

Sugar over DefaultTimeZone, which says which triggers take it and when. A trigger given a zone of its own, Local included, keeps it. Calling this again replaces the default.

UseExecutionLimits(IQuartzBuilder, Action<IServiceProvider, ExecutionLimitsBuilder>)

Configures execution group limits, so resource-hungry jobs cannot saturate every thread. Each limit is counted on this node or across the cluster, as its ExecutionLimitScope says.

public static IQuartzBuilder UseExecutionLimits(this IQuartzBuilder builder, Action<IServiceProvider, ExecutionLimitsBuilder> configure)

Parameters

builder IQuartzBuilder

The builder.

configure Action<IServiceProvider, ExecutionLimitsBuilder>

Configures the limits, given this scheduler's view of the container.

Returns

IQuartzBuilder

Remarks

The same thing as UseExecutionLimits(Action<ExecutionLimitsBuilder>), asked of the container: the callback runs when the scheduler is built rather than when the limits are declared, and is handed this scheduler's view of it. That is what makes a limit read from IOptions<T> the configured number — the options are resolved after the container exists, so everything the application's Configure, PostConfigure and IValidateOptions contribute has been applied by then, which reading configuration at registration time cannot say.

Precedence is unchanged, and is the same for both shapes: the first declaration in code wins, and limits declared in code beat the same limits spelled as quartz.executionLimit.* keys.

It is an extension rather than a member of IQuartzBuilder for the reason the other shapes that take an IServiceProvider are — AddJob, AddTrigger, ScheduleJob, AddCalendar: an implementation of the interface should not have to reproduce it.

UseExecutionLogCapture(IQuartzBuilder, Action<ExecutionLogCaptureOptions>?)

Keeps what each of this scheduler's jobs logs while it runs, and records it with the execution's history row.

public static IQuartzBuilder UseExecutionLogCapture(this IQuartzBuilder builder, Action<ExecutionLogCaptureOptions>? configure = null)

Parameters

builder IQuartzBuilder

The builder.

configure Action<ExecutionLogCaptureOptions>

Sets the bounds; the defaults are 200 entries and 16 KB per execution.

Returns

IQuartzBuilder

Remarks

A line is kept when it is logged through the container's ILoggerFactory on the firing's own flow — by the job, by a service it calls, by work it starts and awaits — from the moment the job starts until the firing ends. Lines logged anywhere else are not kept, so two jobs running at once keep only their own. What is kept is bounded by ExecutionLogCaptureOptions, oldest line dropped first, and lands on Log, which GetExecution(string, string, CancellationToken) reads back.

Two registrations: a logger provider for the container, added once however many schedulers ask, and a middleware for this scheduler, which is what makes its firings the captured ones. The middleware takes its place in the chain where the call is written; register this first to keep what other middleware logs too. The provider is filtered like any other, and its alias for Logging configuration is QuartzExecutionLog.

A Use* call because what it installs is a capability of the scheduler rather than something it holds, as UseTimeZoneConverter is. Without it nothing is captured and nothing is paid: no provider is registered, so no log call anywhere reaches this code.

Exceptions

SchedulerConfigException

Raised when the scheduler is built, around an OptionsValidationException, for a MaxLines below 1 or a MaxBytes below 256.

UseSimpleTypeLoader(IQuartzBuilder)

Uses the default type loader, which resolves type names against loaded assemblies.

public static IQuartzBuilder UseSimpleTypeLoader(this IQuartzBuilder builder)

Parameters

builder IQuartzBuilder

Returns

IQuartzBuilder

Remarks

This is the public way to ask for the built-in loader: SimpleTypeLoader is internal, because a type-loading strategy is not something to derive from, so there is no UseTypeLoader<SimpleTypeLoader>() to write instead. It is also already the default, so calling it only matters where something else registered a loader first.

UseTypeLoader(IQuartzBuilder, Action<TypeLoaderOptions>)

Configures the type loader, which is where a job type that was renamed says what it is called now.

public static IQuartzBuilder UseTypeLoader(this IQuartzBuilder builder, Action<TypeLoaderOptions> configure)

Parameters

builder IQuartzBuilder

The builder.

configure Action<TypeLoaderOptions>

Declares the renames.

Returns

IQuartzBuilder

Remarks

UseTypeLoader<T>() replaces the loader; this configures the one Quartz ships. Declaring a rename here is the declarative form of the rename-aware loader the troubleshooting page used to be the only answer — one line per renamed type instead of a class to write, register and keep — and the same map can arrive from configuration under Quartz:TypeLoader:Aliases, so a rename can ship with the deployment that performs it.

The options are the container's rather than one scheduler's, because the loader is: a rename declared through any scheduler's builder is in force for every scheduler in the container. An alias whose target names no loadable type fails validation at startup.