Quartz i Ninject

Imagem de capa

Hej,

Dziś miałem okazję po raz kolejny użyć Quartz .NET. Dlatego też postanowiłem napisać nowego posta na temat wstrzykiwania zależności w job’ach.

Pewnie niesamowicie spłycając:

Quartz jest to system planowania zadań który umożliwia łatwe tworzenie własnych zadań o ustalonej porze.

Kiedy pierwszy raz usłyszałem o tej bibliotece byłem pełen obaw o to czy to rzeczywiście działa jak należy. Po instalacji i przetestowaniu pierwszych “Jobów” byłem już dobrych myśli. Postanowiliśmy użyć tej biblioteki w naszym projekcie i zastępowaliśmy nasze joby Quartzowymi. Prawie na samym początku okazało się, że może być problem z wstrzykiwaniem zależności w konstruktory tych jobów.

Znalazłem wtedy paczkę Ninject.Extensions.Quartz - nie ufajcie jej :wink: działało, ale po zaktualizowaniu w projekcie Ninject do nowszej wersji - przestało.

To co robi rozszerzenie to rejestracja modułu QuartzNinjectModule w Ninject. Można zrobić to samemu i przechowywać te klasy w projekcie.

Rejestracja modułu w Ninject:

kernel.Load(new QuartzNinjectModule());

Budowa modułu QuartzNinjectModule:

public class QuartzNinjectModule : NinjectModule 
{ 
  public override void Load() 
  { 
      Bind<ISchedulerFactory>().To<NinjectSchedulerFactory>(); 
      Bind<IScheduler>()
          .ToMethod(c => c.Kernel
                          .Get<ISchedulerFactory>()
                          .GetScheduler())
          .InSingletonScope();       
  }
}

W module korzystamy z fabryki planera którą musimy sobie sami stworzyć:

public class NinjectSchedulerFactory : StdSchedulerFactory 
{ 
  private readonly NinjectJobFactory _ninjectJobFactory; 
  
  public NinjectSchedulerFactory(NinjectJobFactory ninjectJobFactory) 
  { 
    _ninjectJobFactory = ninjectJobFactory; 
  } 
  
  protected override IScheduler Instantiate(QuartzSchedulerResources quartzSchedulerResources, QuartzScheduler quartzScheduler) 
  { 
    quartzScheduler.JobFactory = _ninjectJobFactory; 
    
    var scheduler = base.Instantiate(quartzSchedulerResources, quartzScheduler); 
    
    scheduler.Start();
    
    return scheduler;
  } 
}

I ostatnią klasą jaką musimy dodać do projektu to NinjectJobFactory:

public class NinjectJobFactory : IJobFactory 
{ 
    private static readonly ILog Log = LogManager.GetLogger(typeof(NinjectJobFactory));
    private readonly IKernel _kernel; 
    
    static NinjectJobFactory() {}
    
    public NinjectJobFactory(IKernel kernel)
    {
      _kernel = kernel;
    }
    
    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        IJobDetail jobDetail = bundle.JobDetail;   
        Type jobType = jobDetail.JobType;
    
        try 
        {
          Log.Debug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}");
          
          return _kernel.Get(jobType) as IJob;
        } 
        catch (Exception ex)
        {
          throw new SchedulerException($"Problem instantiating class '{jobType.FullName}'", ex);
        }
  } 

  public void ReturnJob(IJob job) {} 
}

Po dodaniu tych klas do projektu, nasz HelloJob mający w konstruktorze zależność będzie działać o ile poprawnie zainicjujemy Scheduler.

[DisallowConcurrentExecution] 
public class BreakerJob : IJob
{ 
    private readonly IUserDataDomain _userDataDomain; 
    
    public BreakerJob(IUserDataDomain userDataDomain) 
    { 
      _userDataDomain = userDataDomain; 
    } 
    
    public void Execute(IJobExecutionContext context) 
    { 
      // DO SOMETHING 
    }
}

Z reguły tworzę sobie klasę pomocniczą o nazwie JobScheduler którego konstruktor wygląda w ten sposób:

public JobScheduler(IScheduler scheduler) 
{ 
    _scheduler = scheduler; RegisterJobs(); 
}

Poza rejestracją jobów umożliwiam uruchomienie IScheduler:

public void Start() 
{ 
    _scheduler.Start(); 
}

I ostatnią rzeczą jaką robię to uruchomienie schedulera - JobScheduler stworzonego w naszym IoC.

    var jobScheduler = _kernel.Get(); 
    jobScheduler.Start();