Dependency Injection (into constructor) with Quartz (job have constructor with params), start and stop a Scheduler with event (button click) in MVC

Viewed 364

I tried to make dependency injection into my Quartz. I need to fire scheduler with my program after clicking a button on my web interface. Generally I had:

this.job = JobBuilder.Create<Program>()
    .WithIdentity("myJob", "group1")
    .Build();

But this above just fire Program with default constructor (Program()).

Unfortanelly I have a program, that need constructor with parameters:

public class Program : IJob, IProgram
    {
        private readonly IDataBaseService _databaseservice;

        public Program(IDataBaseService databaseservice)
        {
            _databaseservice = databaseservice;
        }

        public async Task Execute(IJobExecutionContext context)
        {
            await _databaseservice.DoSmth();
        }

So I need to call Program(IDataBaseService databaseservice)

I've read many guides and tutorials and examples and still I can't find an answer. Most of answers are about Quartz starting at a start of application, but I want it to start with depndency injection after I use button on web interface.

Here is my code:

Startup.cs:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IDataBaseService, DB2Service>();
            services.AddTransient<IJobFactory, MyJobFactory>();
            services.AddSingleton<IMyScheduler, MyScheduler>();
            services.AddSingleton<IProgram, Program>();
            services.AddControllersWithViews();
        }

MVC Controler SiteController.cs:

public class SiteController : Controller
{
private readonly IMyScheduler _MyScheduler;
private readonly IDataBaseService _DataBaseService;
private readonly IProgram _Program;
    
public SiteController(IMyScheduler MyScheduler, IDataBaseService DataBaseService, IProgram Program)
        {
            _MyScheduler = MyScheduler;
            _DataBaseService = DataBaseService;
            _Program = Program;
        }

//this action is called after button click on web interface
public async Task<ActionResult> ButtonClick()
        { 
            //This is where i want to start Quartz. I don't want it to start at start of app.
            //I want it to start when I click button on web interface.
            await MyScheduler.Run_A_Scheduler();
            return OK();
        }
}

MyJobFactory.cs:

public class MyJobFactory : IJobFactory
    {
        private readonly IServiceProvider _provider;

        public MyJobFactory(IServiceProvider provider)
        {
            _provider = provider;
        }
        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            try
            {
                return (IJob)this._provider.GetService(bundle.JobDetail.JobType);
            }
            catch (Exception e)
            {
                throw new SchedulerException(string.Format("Problem while instantiating job '{0}' from the ASPnet Core IOC", bundle.JobDetail.Key), e);
            }
            
        }

        public void ReturnJob(IJob job)
        {
            var disposable = job as IDisposable;
            disposable?.Dispose();
        }
    }

MyScheduler.cs:

public class MyScheduler: IMyScheduler
{
IScheduler scheduler;
IJobDetail job;
ITrigger trigger;

public MyScheduler(IJobFactory myJobFactory)
    {
       scheduler = new StdSchedulerFactory()
          .GetScheduler()
          .Result;
       scheduler.JobFactory = myJobFactory;
    }

public async Task Run_A_Scheduler()//this is a function that I am calling from controler
    {
       this.job = CreateJob<Program>() //definition at the end

       this.trigger = TriggerBuilder.Create()
           .StartNow()
           .WithSimpleSchedule(x => x
                .WithIntervalInSeconds(20)  //example with 20 seconds, normaly every other hour
                .RepeatForever())
           .Build();

       await this.scheduler.ScheduleJob(job, this.trigger);
       await this.scheduler.Start(); 
    }
        
private IJobDetail CreateJob<T>() where T : IJob
        {
            return JobBuilder.Create<T>()
                .WithIdentity(typeof(T).Name)
                .Build();
        }
}

Program.cs

public class Program : IJob, IProgram
    {
        private readonly IDataBaseService _databaseservice;

        public Program(IDataBaseService databaseservice)
        {
            _databaseservice = databaseservice;
        }

        public async Task Execute(IJobExecutionContext context)
        {
            await _databaseservice.DoSmth();
        }

Interfaces IDataBaseService, IMyScheduler, IProgram are mostly empty and just here for dependency injection sake. IJobFactory is from Quartz library.

Please help. I tried many solutions, and still I don't get it. I want to fire with Quartz schedule my Program.cs, which have constructor with parameters. I don't know, how to invoke Program.cs with quartz schedule when I click button on web interface.

1 Answers

This is what I have done to make it work.

in appsettings.json there is a control variable:

    "Generate": true

Then in Startup.cs:

    //adding quartz
    services.AddSingleton<IJobFactory, SingletonJobFactory>();
    services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
    services.AddTransient<AutoTaskGeneration>();
    services.AddSingleton<QuartzJobTaskGeneration>();
    services.AddHostedService<QuartzHostedService>();

SingletonJobFactory:

       public class SingletonJobFactory : IJobFactory
    {
        private readonly IServiceProvider _serviceProvider;
        public SingletonJobFactory(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
        }
        public void ReturnJob(IJob job) { }
    }

AutoTaskGeneration:

public class AutoTaskGeneration
    {
        private readonly ITaskGeneration _taskGeneration;
        private readonly IConfiguration _nativeConfiguration;

        public AutoTaskGeneration(ITaskGeneration taskGeneration, IConfiguration configuration)
        {
            _taskGeneration = taskGeneration; //class that is needed to be run
            _nativeConfiguration = configuration;
        }

        public async Task Run()
        {
            bool Generate = _nativeConfiguration.GetValue<bool>("Generate");
            if (Generate)
            {
                    await _taskGeneration.GenerateTasksRUN(); //function, that i want to run                          
            }
        }
    }

QuartzJobTaskGeneration

[DisallowConcurrentExecution]
    public class QuartzJobTaskGeneration : IJob
    {
        private readonly IServiceProvider _provider;
        public QuartzJobTaskGeneration(IServiceProvider provider)
        {
            _provider = provider;
        }

        public async Task Execute(IJobExecutionContext context)
        {
            using (var scope = _provider.CreateScope())
            {
                // Resolve the Scoped service
                var service = scope.ServiceProvider.GetService<AutoTaskGeneration>();
                await service.Run();
            }
        }
    }

QuartzHostedService

 public class QuartzHostedService : IHostedService
    {
        private readonly ISchedulerFactory _schedulerFactory;
        private readonly IJobFactory _jobFactory;
        private readonly QuartzJobTaskGeneration _quartzJobTaskGeneration;

        public QuartzHostedService(
            ISchedulerFactory schedulerFactory,
            IJobFactory jobFactory,
            QuartzJobTaskGeneration quartzJobTaskGeneration,
            )
        {
            _schedulerFactory = schedulerFactory;
            _jobFactory = jobFactory;
            _quartzJobTaskGeneration = quartzJobTaskGeneration;
           

        }
        public IScheduler Scheduler { get; set; }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
            Scheduler.JobFactory = _jobFactory;

            await Scheduler.UnscheduleJob(new TriggerKey("QuartzJobTaskGeneration.trigger", "QuartzJobTaskGenerationGroup"));

            var job = JobBuilder
            .Create<QuartzJobTaskGeneration>()
            .WithIdentity("QuartzJobTaskGeneration", "QuartzJobTaskGenerationGroup")
            .WithDescription("QuartzJobTaskGeneration")
            .Build();

            var trigger = TriggerBuilder
            .Create()
            .WithIdentity($"QuartzJobTaskGeneration.trigger", "QuartzJobTaskGenerationGroup")
            .WithCronSchedule("0 30 7 ? * MON-SAT *")
            .WithDescription("QuartzJobTaskGeneration.trigger")
            .Build();
         }
}

And now with all of this I can control when program should fire based on his cron and Generate value in appsetings

Related