Skip to content

Add factory-based AddQuartz() overload with IServiceProvider access#3007

Merged
lahma merged 5 commits into
3.xfrom
factory-addquartz
Apr 5, 2026
Merged

Add factory-based AddQuartz() overload with IServiceProvider access#3007
lahma merged 5 commits into
3.xfrom
factory-addquartz

Conversation

@lahma

@lahma lahma commented Apr 5, 2026

Copy link
Copy Markdown
Member

Summary

Closes #1617

  • Add new AddQuartz overloads accepting Action<IServiceCollectionQuartzConfigurator, IServiceProvider>, allowing users to resolve DI services during Quartz configuration (e.g. to obtain connection strings from a DatabaseConnectionFactory)
  • The configuration delegate is deferred until IOptions<QuartzOptions>.Value is accessed, at which point IServiceProvider is available
  • All configurator operations (properties, AddJob, AddTrigger, ScheduleJob, AddCalendar, listeners) work transparently in the deferred context via a DeferredServiceCollection wrapper
  • Supports default schedulers, named schedulers, and NameValueCollection property overloads
  • No breaking changes — only additive new overloads and internal plumbing

New API

// Resolve services during Quartz configuration
services.AddQuartz((q, sp) =>
{
    var dbFactory = sp.GetRequiredService<DatabaseConnectionFactory>();
    q.UsePersistentStore(s =>
    {
        s.UseSqlServer(sqlServer =>
        {
            sqlServer.ConnectionString = dbFactory.GetConnectionString();
        });
    });
});

// Also works with named schedulers
services.AddQuartz("MyScheduler", (q, sp) => { ... });

// And with initial properties
services.AddQuartz(properties, (q, sp) => { ... });

Implementation

  • DeferredQuartzConfigurationIConfigureNamedOptions<QuartzOptions> that defers the user's configure lambda
  • DeferredServiceCollectionIServiceCollection wrapper that intercepts service registrations (jobs, triggers, listeners, calendars) and applies them directly to QuartzOptions
  • ListenerCreationHelper — Shared static helper extracted from NamedSchedulerFactory for creating listener instances from configuration objects
  • ServiceCollectionSchedulerFactory and NamedSchedulerFactory updated to process deferred listeners/calendars

Test plan

  • 23 new tests covering:
    • Property configuration with IServiceProvider (scheduler name, connection strings, IOptions<T>)
    • AddJob/AddTrigger/ScheduleJob/AddCalendar in deferred lambda
    • Scheduler and job listener registration in deferred lambda
    • Named scheduler isolation with deferred config
    • Combined immediate + deferred configuration (accumulation and override)
    • Full scheduler creation integration tests (listeners wired, calendars added, jobs scheduled)
    • End-to-end API proof matching the original issue scenario
  • All 1307 existing unit tests pass — no regressions

🤖 Generated with Claude Code

@lahma lahma added the port-main Requires porting to main branch label Apr 5, 2026
@lahma
lahma requested a review from Copilot April 5, 2026 08:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds new DI extension overloads for AddQuartz(...) that provide IServiceProvider access during Quartz configuration by deferring the user’s configuration delegate until QuartzOptions is materialized. It introduces internal plumbing to capture deferred registrations (jobs/triggers/calendars/listeners) and applies them during scheduler initialization for both default and named schedulers.

Changes:

  • Add AddQuartz overloads accepting Action<IServiceCollectionQuartzConfigurator, IServiceProvider> (default + named + properties variants) with deferred execution via options configuration.
  • Introduce DeferredQuartzConfiguration + DeferredServiceCollection to apply deferred configurator operations into QuartzOptions.
  • Update scheduler factories to process deferred listeners/calendars; add extensive unit/integration-style tests for deferred behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Quartz.Extensions.DependencyInjection/ServiceCollectionExtensions.cs Adds new factory-based AddQuartz overloads that defer configuration until options are resolved.
src/Quartz.Extensions.DependencyInjection/DeferredQuartzConfiguration.cs Implements deferred IConfigureNamedOptions<QuartzOptions> execution with IServiceProvider access.
src/Quartz.Extensions.DependencyInjection/DeferredServiceCollection.cs Wraps IServiceCollection to intercept Quartz-related registrations and apply them directly to QuartzOptions.
src/Quartz.Extensions.DependencyInjection/QuartzOptions.cs Adds internal lists to store deferred listeners/calendars.
src/Quartz.Extensions.DependencyInjection/ServiceCollectionSchedulerFactory.cs Applies deferred listeners/calendars for the default scheduler during initialization.
src/Quartz.Extensions.DependencyInjection/NamedSchedulerFactory.cs Applies deferred listeners/calendars for named schedulers; extracts listener creation into helper.
src/Quartz.Extensions.DependencyInjection/ListenerCreationHelper.cs Shared helper for creating listener instances from configuration records.
src/Quartz.Tests.Unit/Extensions/DependencyInjection/DeferredQuartzConfigurationTests.cs New test suite covering deferred properties, registrations, named scheduler isolation, and end-to-end scheduler creation scenarios.

Comment thread src/Quartz.Extensions.DependencyInjection/DeferredServiceCollection.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Comment thread src/Quartz.Extensions.DependencyInjection/DeferredServiceCollection.cs Outdated
Comment thread src/Quartz.Extensions.DependencyInjection/DeferredServiceCollection.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

lahma and others added 5 commits April 5, 2026 15:28
…1617)

Add new AddQuartz overloads that accept Action<IServiceCollectionQuartzConfigurator, IServiceProvider>,
allowing users to resolve DI services (e.g. connection string providers) during Quartz configuration.
The configuration delegate is deferred until IOptions<QuartzOptions>.Value is accessed, at which
point IServiceProvider is available.

Supports default schedulers, named schedulers, and NameValueCollection property overloads.
All configurator operations (properties, AddJob, AddTrigger, ScheduleJob, AddCalendar, listeners)
work transparently in the deferred context via a DeferredServiceCollection wrapper.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Suppress IJobListener/ITriggerListener interceptions in DeferredServiceCollection
  since the *Configuration entries (with matchers) already handle them
- Discard unhandled registrations instead of adding to the original IServiceCollection
- Add duplicate-listener regression test assertion

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Make DeferredServiceCollection fully immutable: indexer setter, Clear,
  Remove, RemoveAt are no-ops; IsReadOnly returns true
- Add doc comment on ResolveInstance explaining why null for type-only
  descriptors is safe
- Replace verbose 21-method TestSchedulerListener and TestJobListener
  with SchedulerListenerSupport / JobListenerSupport base classes

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Only suppress the interface registration when a matching *Configuration
has already been captured. If no configuration exists (e.g., a listener
registered only via the interface), create a fallback configuration so
the listener is never silently dropped.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Revert conditional IJobListener/ITriggerListener suppression to
  unconditional: the conditional check failed for factory/instance-based
  descriptors where ImplementationType is null, causing duplicates.
  Unconditional suppression is safe because Services is internal and all
  listener registrations go through configurator methods that always emit
  the paired *Configuration.
- Re-force scheduler instance name for named schedulers after deferred
  lambda runs, matching the guard in the immediate AddQuartz overload.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@lahma
lahma force-pushed the factory-addquartz branch from b6e2b9f to 6d69ab2 Compare April 5, 2026 12:29
@lahma
lahma merged commit 08ec695 into 3.x Apr 5, 2026
14 checks passed
@lahma
lahma deleted the factory-addquartz branch April 5, 2026 12:33
@sonarqubecloud

sonarqubecloud Bot commented Apr 5, 2026

Copy link
Copy Markdown

lahma added a commit that referenced this pull request Apr 5, 2026
Port of 3.x PR #3007 to main (4.x). Adds AddQuartz overloads accepting
Action<IServiceCollectionQuartzConfigurator, IServiceProvider> for
deferred configuration with access to DI services.

Adapted for 4.x: files in src/Quartz/Configuration/, ValueTask returns,
collection expressions, ArgumentNullException.ThrowIfNull, no
MicrosoftLoggingProvider setup, updated ADO provider API signatures.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@lahma lahma removed the port-main Requires porting to main branch label Apr 5, 2026
lahma added a commit that referenced this pull request Apr 5, 2026
* Port factory-based AddQuartz() with IServiceProvider to 4.x (#1617)

Port of 3.x PR #3007 to main (4.x). Adds AddQuartz overloads accepting
Action<IServiceCollectionQuartzConfigurator, IServiceProvider> for
deferred configuration with access to DI services.

Adapted for 4.x: files in src/Quartz/Configuration/, ValueTask returns,
collection expressions, ArgumentNullException.ThrowIfNull, no
MicrosoftLoggingProvider setup, updated ADO provider API signatures.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* Add test proving UsePersistentStore property configuration works in deferred lambda

Verifies that connection string, job store type, driver delegate, and
clustering properties are all correctly set when configured through the
deferred AddQuartz overload — covering the primary use case from #1617.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants