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
builderIQuartzBuilderThe builder.
namestringThe name triggers refer to the calendar by.
calendarICalendarThe calendar.
optionsAddCalendarOptionsHow 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
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
builderIQuartzBuilderThe builder.
namestringThe name triggers refer to the calendar by.
factoryFunc<IServiceProvider, ICalendar>Builds the calendar from the container.
optionsAddCalendarOptionsHow 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
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
builderIQuartzBuilderThe builder.
namestringThe name triggers refer to the calendar by.
optionsAddCalendarOptionsHow the calendar is added: whether it replaces one of the same name, and whether triggers using that name are recomputed. Defaults to replacing nothing.
configureAction<IServiceProvider, T>Configures the calendar, which is created with its default constructor.
Returns
Type Parameters
TThe 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
builderIQuartzBuilderThe builder.
namestringThe name triggers refer to the calendar by.
optionsAddCalendarOptionsHow the calendar is added: whether it replaces one of the same name, and whether triggers using that name are recomputed. Defaults to replacing nothing.
configureAction<T>Configures the calendar, which is created with its default constructor.
Returns
Type Parameters
TThe 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
builderIQuartzBuilderThe builder.
namestringThe job's name, which makes its key in the default group unless
configuregives it another identity.handlerDelegateThe job's code.
configureAction<IJobConfigurator<IJob>>Configures the job, which most jobs do not need to.
Returns
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
nameis empty, orhandlercannot be run as a job: it returns something other than ValueTask, Task or nothing, isasync void, takes aref,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
builderIQuartzBuilderThe builder.
namestringThe job's name, which makes its key in the default group unless
configuregives it another identity.handlerDelegateThe job's code.
configureAction<IServiceProvider, IJobConfigurator<IJob>>Configures the job, given this scheduler's view of the container.
Returns
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
nameis empty, orhandlercannot be run as a job: it returns something other than ValueTask, Task or nothing, isasync void, takes aref,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
builderIQuartzBuilderThe builder.
jobTypeTypeThe job's type, which must implement IJob.
configureAction<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
Remarks
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
builderIQuartzBuilderThe builder.
jobTypeTypeThe job's type, which must implement IJob.
configureAction<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
Remarks
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
builderIQuartzBuilderThe builder.
Returns
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
builderIQuartzBuilderThe builder.
defaultTimeoutFunc<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
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
defaultTimeoutanswered 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
builderIQuartzBuilderThe builder.
defaultTimeoutTimeSpan?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
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
defaultTimeoutis 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
builderIQuartzBuilderThe builder.
Returns
Type Parameters
TJobThe 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
builderIQuartzBuilderThe builder.
lifetimeServiceLifetimeHow long one instance lives.
Returns
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
builderIQuartzBuilderThe builder.
implementationFactoryFunc<IServiceProvider, TJob>Builds the job.
Returns
Type Parameters
TJobThe 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
builderIQuartzBuilderThe builder.
implementationFactoryFunc<IServiceProvider, TJob>Builds the job.
lifetimeServiceLifetimeHow long one instance lives.
Returns
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
builderIQuartzBuilderThe builder.
Returns
Type Parameters
TJobThe job type, as named on the job detail.
TImplementationThe 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
builderIQuartzBuilderThe builder.
lifetimeServiceLifetimeHow long one instance lives.
Returns
Type Parameters
TJobTImplementation
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
builderIQuartzBuilderThe builder.
configureAction<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
Type Parameters
TThe job's type.
Remarks
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
builderIQuartzBuilderThe builder.
configureAction<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
Type Parameters
TThe job's type.
Remarks
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
builderIQuartzBuilderThe builder.
configureAction<ITriggerConfigurator<IJob>>Configures the trigger, which must name its job with
ForJob.
Returns
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
builderIQuartzBuilderThe builder.
configureAction<IServiceProvider, ITriggerConfigurator<IJob>>Configures the trigger, which must name its job with
ForJob.
Returns
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
builderIQuartzBuilderThe builder.
configureAction<ITriggerConfigurator<TJob>>Configures the trigger.
Returns
Type Parameters
TJobThe 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
builderIQuartzBuilderThe builder.
configureAction<IServiceProvider, ITriggerConfigurator<TJob>>Configures the trigger.
Returns
Type Parameters
TJobThe 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
builderIQuartzBuilderThe builder.
configureAction<IServiceScope, TriggerFiredBundle, IScheduler>Prepares the scope, given the scope, the fire it was opened for and the scheduler firing it.
Returns
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
builderIQuartzBuilderThe builder.
Returns
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
builderIQuartzBuilderThe builder.
groupsstring[]The execution groups this node refuses.
Returns
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
builderIQuartzBuilderThe builder.
jobKeyJobKeyThe job to run.
Returns
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
builderIQuartzBuilderThe builder.
namestringThe trigger's name, and so the job's, unless the trigger is given another.
handlerDelegateThe job's code.
triggerAction<ITriggerConfigurator<IJob>>Configures the trigger.
Returns
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
nameis empty, orhandlercannot be run as a job: it returns something other than ValueTask, Task or nothing, isasync void, takes aref,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
builderIQuartzBuilderThe builder.
namestringThe trigger's name, and so the job's, unless the trigger is given another.
handlerDelegateThe job's code.
triggerAction<IServiceProvider, ITriggerConfigurator<IJob>>Configures the trigger, given this scheduler's view of the container.
Returns
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
nameis empty, orhandlercannot be run as a job: it returns something other than ValueTask, Task or nothing, isasync void, takes aref,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
builderIQuartzBuilderThe builder.
triggerAction<ITriggerConfigurator<T>>Configures the trigger.
jobAction<IJobConfigurator<T>>Configures the job, which most schedules do not need to.
Returns
Type Parameters
TThe 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.
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
builderIQuartzBuilderThe builder.
triggerAction<IServiceProvider, ITriggerConfigurator<T>>Configures the trigger.
jobAction<IServiceProvider, IJobConfigurator<T>>Configures the job, which most schedules do not need to.
Returns
Type Parameters
TThe 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.
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
builderIQuartzBuilderThe builder.
groupsstring[]The execution groups this node serves.
Returns
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
builderIQuartzBuilderThe builder.
policyRetryPolicyThe policy, for example
RetryPolicy.Exponential(5, TimeSpan.FromSeconds(30)).
Returns
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
builderIQuartzBuilderThe builder.
timeZoneTimeZoneInfoThe zone, for example
TimeZoneInfo.UtcorTimeZones.FindById("Europe/Helsinki").
Returns
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
builderIQuartzBuilderThe builder.
configureAction<IServiceProvider, ExecutionLimitsBuilder>Configures the limits, given this scheduler's view of the container.
Returns
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
builderIQuartzBuilderThe builder.
configureAction<ExecutionLogCaptureOptions>Sets the bounds; the defaults are 200 entries and 16 KB per execution.
Returns
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
1or a MaxBytes below256.
UseSimpleTypeLoader(IQuartzBuilder)
Uses the default type loader, which resolves type names against loaded assemblies.
public static IQuartzBuilder UseSimpleTypeLoader(this IQuartzBuilder builder)
Parameters
builderIQuartzBuilder
Returns
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
builderIQuartzBuilderThe builder.
configureAction<TypeLoaderOptions>Declares the renames.
Returns
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.