Search code examples
c#dependency-injectionninjectlog4netquartz.net

How to configure ninject with Quartz.net and log4net


I try to make quartz and log4net work with ninject.

Ninject with log4net (without quartz) works perfect.

But when I use with Quartz, jobs executed but logger not working. (empty console)

Below is Ninject configuration:

var kernel = new StandardKernel();

kernel.Load(Assembly.GetExecutingAssembly());

kernel.Bind<ILog>().ToMethod(x => LogManager.GetLogger(x.Request.Target != null
                        ? x.Request.Target.Member.DeclaringType
                        : x.Request.Service));

kernel.Bind<IScheduler>().ToMethod(x =>
{
    ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
    var sched = schedulerFactory.GetScheduler().Result;
    sched.JobFactory = new NinjectJobFactory(kernel);
    return sched;
}); 

and ninjetJobFactory:

class NinjectJobFactory : SimpleJobFactory
{
    readonly IKernel _kernel;

    public NinjectJobFactory(IKernel kernel)
    {
        this._kernel = kernel;
    }

    public override IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        try
        {
            // this will inject dependencies that the job requires
            return (IJob)this._kernel.Get(bundle.JobDetail.JobType);
        }
        catch (Exception e)
        {
            throw new SchedulerException(string.Format("Problem while instantiating job '{0}' from the NinjectJobFactory.", bundle.JobDetail.Key), e);
        }
    }
}

I Used this article to make Quarts work with ninject.

I made simple working example, you can download it from github for testing.

Thanks for help.


Solution

  • While investigating the issue I realized that by simply resolving the ILog from the kernel before resolving the scheduler seemed to work. As to the reason why I was a bit uncertain.

    I did notice however that the version of Quartz being used has an asynchronous API so had to make some changes during my investigation of the problem.

    In the comments of the link documentation, it was suggested to refactor to new async API syntax

    kernel.Bind<Task<IScheduler>>().ToMethod(async _ => {
        ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
        var sched = await schedulerFactory.GetScheduler();
        sched.JobFactory = new NinjectJobFactory(kernel);
        return sched;
    });
    

    I improved on this by creating a service that tales advantage of the async API when starting the scheduler.

    public interface IService {
        Task Start();
    }
    
    public class Service : IService {
        private readonly IJobFactory jobFactory;
        private readonly ILog log;
        private readonly ISchedulerFactory schedulerFactory;
    
        public Service(IJobFactory jobFactory, ISchedulerFactory schedulerFactory, ILog log) {
            this.jobFactory = jobFactory;
            this.schedulerFactory = schedulerFactory;
            this.log = log;
        }
    
        public async Task Start() {
            IScheduler scheduler = await schedulerFactory.GetScheduler();
            scheduler.JobFactory = jobFactory;
    
            IJobDetail job = JobBuilder.Create()
               .OfType<SimpleJob>()
               .Build();
    
            ITrigger trigger = TriggerBuilder.Create()
                .WithIdentity("trigger1", "group1")
                .StartNow()
                .WithSimpleSchedule(x => x.WithIntervalInSeconds(3)
                    .RepeatForever())
                .Build();
    
            await scheduler.ScheduleJob(job, trigger);
    
            await scheduler.Start();
    
            log.Debug("Scheduler started");
        }
    }
    

    I created a bootstrapper and used the NinjectModules to properly organize dependency injections

    public class Bootstrapper {
        public IKernel Init() {
            var kernel = new StandardKernel();
            kernel.Load(Assembly.GetExecutingAssembly());
    
            kernel.Bind<IServiceProvider>().ToConstant(kernel);
    
            return kernel;
        }
    }
    
    public class LoggingModule : NinjectModule {
        public override void Load() {
            Bind<ILog>()
                .ToMethod(x => LogManager.GetLogger(
                    x.Request.Target != null ? x.Request.Target.Member.DeclaringType : x.Request.Service)
                );
        }
    }
    
    public class QuartzModule : NinjectModule {
        public override void Load() {
            Bind<IJobFactory>().To<NinjectJobFactory>();
            Bind<ISchedulerFactory>().To<StdSchedulerFactory>();
            Bind<IService>().To<Service>();
        }
    }
    
    class NinjectJobFactory : IJobFactory {
        readonly IServiceProvider provider;
    
        public NinjectJobFactory(IServiceProvider provider) {
            this.provider = provider;
        }
    
        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) {
            IJobDetail jobDetail = bundle.JobDetail;
            Type jobType = jobDetail.JobType;
            try {
                // this will inject any dependencies that the job requires
                var value = (IJob)provider.GetService(jobType);
                return value;
            } catch (Exception e) {
                throw new SchedulerException(string.Format("Problem while instantiating job '{0}' from the NinjectJobFactory.", bundle.JobDetail.Key), e);
            }
        }
    
        /// <summary>
        /// Allows the job factory to destroy/cleanup the job if needed. 
        /// No-op when using SimpleJobFactory.
        /// </summary>
        public void ReturnJob(IJob job) {
            var disposable = job as IDisposable;
            disposable?.Dispose();
        }
    }
    

    With that modification and updating the main program accordingly

    class Program {
        static void Main(string[] args) {
            Bootstrapper bootstrapper = new Bootstrapper();
            var kernel = bootstrapper.Init();
    
            WithQuartz(kernel).GetAwaiter().GetResult();
    
            Console.ReadKey();
        }
    
        public static Task WithQuartz(IKernel kernel) {
            var service = kernel.Get<IService>();
            return service.Start();
        }
    }
    

    everything seems to work as expected.