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

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


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.


Solution

  • 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