quartznet icon indicating copy to clipboard operation
quartznet copied to clipboard

Cannot find IDirectoryScanListener when DirectoryScanJob is executed

Open mcattle opened this issue 3 years ago • 2 comments

Describe the bug

After registering a DirectoryScanJob with an associated trigger, when the job is invoked, an exception is thrown:

 Quartz.JobExecutionException: IDirectoryScanListener named 'LogChangesListener' not found in SchedulerContext
   at Quartz.Job.DirectoryScanJobModel.GetListener(JobDataMap mergedJobDataMap, SchedulerContext schedCtxt)
   at Quartz.Job.DirectoryScanJobModel.GetInstance(IJobExecutionContext context)
   at Quartz.Job.DirectoryScanJob.Execute(IJobExecutionContext context)
   at Quartz.Simpl.MicrosoftDependencyInjectionJobFactory.ScopedJob.Execute(IJobExecutionContext context)
   at Quartz.Core.JobRunShell.Run(CancellationToken cancellationToken)

Version used

.NET 6.0 Quartz 3.4.0 Quartz.Extensions.DependencyInjection 3.4.0 Quartz.Extensions.Hosting 3.4.0 Quartz.Jobs 3.4.0

To Reproduce

I add Quartz to the service application as follows:

services.AddQuartz(q =>
        {
            q.UseMicrosoftDependencyInjectionJobFactory();
            q.AddDirectoryListeners(hostContext.Configuration, services);
        })
        .AddQuartzHostedService(options =>
        {
            options.WaitForJobsToComplete = true;
            options.AwaitApplicationStarted = true;
        });

My AddDirectoryListeners() extension is written as follows, in order to dynamically add DirectoryScanJobs based on appsettings.json configuration:

public static IServiceCollectionQuartzConfigurator AddDirectoryListeners(
    this IServiceCollectionQuartzConfigurator quartz,
    IConfiguration config,
    IServiceCollection services)
{
    var listeners = Assembly.GetExecutingAssembly()
        .GetTypes()
        .Where(type => typeof(IDirectoryScanListener).IsAssignableFrom(type) && !type.IsInterface);

    foreach (var listener in listeners)
    {
        services.AddTransient(listener);
    }

    var listenerConfigs = config.GetSection("Quartz")
        .GetChildren()
        .Where(c => c.Key.EndsWith("Listener"))
        .ToList();

    foreach (var listenerConfig in listenerConfigs)
    {
        string listenerName = listenerConfig.Key;
        string jobSchedule = config[$"Quartz:{listenerName}:Schedule"];
        string jobPaths = config[$"Quartz:{listenerName}:Paths"];

        var jobKey = new JobKey(listenerName, "DirectoryScanners");

        quartz.AddJob<DirectoryScanJob>(opts => opts
            .WithIdentity(jobKey)
            .UsingJobData(DirectoryScanJob.DirectoryNames, jobPaths)
            .UsingJobData(DirectoryScanJob.DirectoryScanListenerName, listenerName));

        quartz.AddTrigger(opts => opts
            .ForJob(jobKey)
            .WithIdentity(listenerName + "-trigger")
            .WithCronSchedule(jobSchedule));
    }

    return quartz;
}

By convention, my IDirectoryScanListener classes end with "Listener" and are configured in appsettings.json with the class name as follows:

{
  "Quartz": {
    "LogChangesListener": {
      "Schedule": "15/30 * * * * ?",
      "Paths": "C:\\Temp\\Logs;C:\\Temp\\Other" 
    }
  }
}

Finally, my sample listener class. Note that it uses DI, so I'd hope it would be instantiated at the time of the job executing:

public class LogChangesListener : IDirectoryScanListener
{
    private readonly ILogger<LogChangesListener> _log;

    public LogChangesListener(ILogger<LogChangesListener> logger)
    {
        _log = logger;
    }

    public void FilesDeleted(IReadOnlyCollection<FileInfo> deletedFiles)
    {
        foreach (FileInfo file in deletedFiles)
            _log.LogInformation("Deleted file {fileName}", file.Name);
    }

    public void FilesUpdatedOrAdded(IReadOnlyCollection<FileInfo> updatedFiles)
    {
        foreach (FileInfo file in updatedFiles)
            _log.LogInformation("Added/Updated file {fileName}", file.Name);
    }
}

Expected behavior

I'd expect that when the DirectoryScanJob executes, the appropriate IDirectoryScanListener as registered with DirectoryScanJob.DirectoryScanListenerName gets instantiated and used.

mcattle avatar Apr 29 '22 16:04 mcattle

Did you get this working already?

danielwagn3r avatar May 09 '22 20:05 danielwagn3r

Did you get this working already?

No, it's still not working.

mcattle avatar May 09 '22 20:05 mcattle