Search code examples
c#asp.net-coredependency-injectionquartz.net

Registering custom JobConfiguration setup to get DI abilities with Quartz.NET


I have simple job:

[DisallowConcurrentExecution]
public class OutgoingRegistriesJob : IJob {
    private readonly ILogger<OutgoingRegistriesJob> _logger;

    public OutgoingRegistriesJob(ILogger<OutgoingRegistriesJob> logger) {
        _logger = logger;
    }

    public Task Execute(IJobExecutionContext context) {
        _logger.LogInformation("{UtcNow}", DateTime.UtcNow);
        return Task.CompletedTask;
    }
}

This is how I register it to a service pipeline:

public static class QuartzExtensions {
    public static IServiceCollection AddQuartzSetup(this IServiceCollection services) {
        services.AddQuartz(opts => {
            // => this is obsolete... what should I use instead?
            // opts.UseMicrosoftDependencyInjectionJobFactory();
        });
        services.AddQuartzHostedService(options => { options.WaitForJobsToComplete = true; });
        services.ConfigureOptions<UpdateS3BackgroundJobSetup>();

        return services;
    }
}

This is the configuration: If I remove the DbContext it's not failing so it's seems like a kinda of DI issue

    public class UpdateS3BackgroundJobSetup : IConfigureOptions<QuartzOptions> {
        private readonly IApplicationConfigurationDbContext _dbContext;
    
        public UpdateS3BackgroundJobSetup(
            IApplicationConfigurationDbContext dbContext
        ) {
            _dbContext = dbContext;
        }
    
        public async void Configure(QuartzOptions options) {
            var configuration =
                await _dbContext.ApplicationConfigurations
                    .Where(cfg => cfg.Name == "metamorph-db")
                    .FirstOrDefaultAsync();
    
            var jobKey = JobKey.Create(nameof(OutgoingRegistriesJob));
    
            options
                .AddJob<OutgoingRegistriesJob>(jobBuilder => jobBuilder.WithIdentity(jobKey))
                .AddTrigger(trigger =>
                    trigger
                        .ForJob(jobKey)
                        .WithSimpleSchedule(schedule =>
                            schedule.WithIntervalInMinutes(5).RepeatForever()));
        }
    }

Error:
Unhandled exception. System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Quartz.Spi.IJobFactory Lifetime: Singleton ImplementationType: Quartz.Simpl.MicrosoftDependencyInjectionJobFactory': Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.) (Error while validating the service descriptor 'ServiceType: Quartz.ContainerConfigurationProcessor Lifetime: Singleton ImplementationType: Quartz.ContainerConfigurationProcessor': Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.) (Error while validating the service descriptor 'ServiceType: Quartz.ISchedulerFactory Lifetime: Singleton ImplementationType: Quartz.ServiceCollectionSchedulerFactory': Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.) (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: Quartz.QuartzHostedService': Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.)
 ---> System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Quartz.Spi.IJobFactory Lifetime: Singleton ImplementationType: Quartz.Simpl.MicrosoftDependencyInjectionJobFactory': Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.
 ---> System.InvalidOperationException: Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitScopeCache(ServiceCallSite scopedCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateCallSite(ServiceCallSite callSite)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at Microsoft.Extensions.Hosting.HostApplicationBuilder.Build()
   at Microsoft.AspNetCore.Builder.WebApplicationBuilder.Build()
   at Program.<Main>$(String[] args) in /Users/genady/dev/ar-dotnet-workspace/MetamorphDB/Api/Program.cs:line 52
 ---> (Inner Exception #1) System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Quartz.ContainerConfigurationProcessor Lifetime: Singleton ImplementationType: Quartz.ContainerConfigurationProcessor': Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.
 ---> System.InvalidOperationException: Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitScopeCache(ServiceCallSite scopedCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateCallSite(ServiceCallSite callSite)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)<---

 ---> (Inner Exception #2) System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Quartz.ISchedulerFactory Lifetime: Singleton ImplementationType: Quartz.ServiceCollectionSchedulerFactory': Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.
 ---> System.InvalidOperationException: Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitScopeCache(ServiceCallSite scopedCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateCallSite(ServiceCallSite callSite)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)<---

 ---> (Inner Exception #3) System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: Quartz.QuartzHostedService': Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.
 ---> System.InvalidOperationException: Cannot consume scoped service 'Application.Common.Interfaces.IApplicationConfigurationDbContext' from singleton 'Microsoft.Extensions.Options.IOptions`1[Quartz.QuartzOptions]'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitScopeCache(ServiceCallSite scopedCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateCallSite(ServiceCallSite callSite)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)<---

Solution

  • Issue is indeed related to dependency injection. IConfigureOptions is Singleton thus you cannot inject a scoped service like IApplicationConfigurationDbContext. But you can create a scope and resolve manually.

     public class UpdateS3BackgroundJobSetup : IConfigureOptions<QuartzOptions> {
    private readonly IServiceProvider _provider;
    
    public UpdateS3BackgroundJobSetup(
        IServiceProvider provider
    ) {
        _provider = provider;
    }
    
    public async void Configure(QuartzOptions options) {
         // Create a new scope
         using (var scope = _provider.CreateScope())
         {
            // Resolve the Scoped service
            var dbContext = scope.ServiceProvider.GetService<IApplicationConfigurationDbContext>();
             
            var configuration =
                await dbContext.ApplicationConfigurations
                    .Where(cfg => cfg.Name == "metamorph-db")
                    .FirstOrDefaultAsync();
    
            var jobKey = JobKey.Create(nameof(OutgoingRegistriesJob));
    
            options
                .AddJob<OutgoingRegistriesJob>(jobBuilder => jobBuilder.WithIdentity(jobKey))
                .AddTrigger(trigger =>
                    trigger
                        .ForJob(jobKey)
                        .WithSimpleSchedule(schedule =>
                            schedule.WithIntervalInMinutes(5).RepeatForever()));
        }
    }
    

    }