.NET Core中的Quartz依赖注入

33
我该如何在 .net core 中配置 Quartz 以使用依赖注入?我正在使用标准的 .net core 依赖机制。在实现 IJob 接口的类的构造函数中,我需要注入一些依赖项。

1
你应该升级到Quartz 3.1,并开始使用内置的依赖注入支持 - Marko Lahma
6个回答

36
您可以使用Quartz.Spi.IJobFactory接口并实现它。Quartz文档说明如下:
当触发器触发时,与之关联的作业将通过调度程序上配置的JobFactory实例化。默认的JobFactory只是激活作业类的新实例。您可能希望创建自己的JobFactory实现,以完成诸如让应用程序的IoC或DI容器生成/初始化作业实例等任务。请参见IJobFactory接口和相关的Scheduler.SetJobFactory(fact)方法。
ISchedulerFactory schedulerFactory = new StdSchedulerFactory(properties);
var scheduler = schedulerFactory.GetScheduler();

scheduler.JobFactory = jobFactory;

编辑

实现可以如下所示:

public class JobFactory : IJobFactory
{
    protected readonly IServiceProvider Container;

    public JobFactory(IServiceProvider container)
    {
        Container = container;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        return Container.GetService(bundle.JobDetail.JobType) as IJob;
    }

    public void ReturnJob(IJob job)
    {
        // i couldn't find a way to release services with your preferred DI, 
        // its up to you to google such things
    }
}

要将其与Microsoft.Extensions.DependencyInjection一起使用,请按以下方式创建容器:
var services = new ServiceCollection();
services.AddTransient<IAuthorizable, AuthorizeService>();
var container = services.BuildServiceProvider();
var jobFactory = new JobFactory(container);

参考资料

  1. Quartz 文档

  2. API


我知道这个,但我应该如何构建“jobFactory”?我用IJobFactory构建类,但我不知道如何编写“NewJob”和“ReturnJob”。 - donex93
我知道这个,但是我应该如何构建“jobFactory”?这并不是你的问题,你想知道如何配置Quartz来使用DI,这是你问题的正确答案。关于如何实现接口,取决于你的DI容器。看一下我编辑后的答案,以了解我在一些项目中使用的解决方案。 - Rabban
在标准的 .net core 中,TypeFactory 是什么类型? - donex93
2
@Zaphod JobFactory仅由Quartz需要,您可以像没有工厂一样安排作业。 您可以查看我的其他答案,以显示调度:带有助手方法的作业和触发器创建一个作业的多个触发器作业重新调度。 但请注意,似乎CronTrigger调度存在错误 - Rabban
@Rabban 谢谢!我没有在调度程序上设置工作工厂。当我这样做时,一切都很好。 - Zaphod
显示剩余5条评论

25

受到Rabbans优秀回答的启发,我创建了一个完整的Microsoft.Extensions.DependencyInjection作业工厂实现:

实现

using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Spi;
using System;
using System.Collections.Concurrent;

class JobFactory : IJobFactory
{
    protected readonly IServiceProvider _serviceProvider;

    protected readonly ConcurrentDictionary<IJob, IServiceScope> _scopes = new ConcurrentDictionary<IJob, IServiceScope>();

    public JobFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        var scope = _serviceProvider.CreateScope();
        IJob job;

        try
        {
            job = scope.ServiceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
        }
        catch
        {
            // Failed to create the job -> ensure scope gets disposed
            scope.Dispose();
            throw;
        }

        // Add scope to dictionary so we can dispose it once the job finishes
        if (!_scopes.TryAdd(job, scope))
        {
            // Failed to track DI scope -> ensure scope gets disposed
            scope.Dispose();
            throw new Exception("Failed to track DI scope");
        }

        return job;
    }

    public void ReturnJob(IJob job)
    {
        if (_scopes.TryRemove(job, out var scope))
        {
            // The Dispose() method ends the scope lifetime.
            // Once Dispose is called, any scoped services that have been resolved from ServiceProvider will be disposed.
            scope.Dispose();
        }
    }
}

使用方法

// Prepare the DI container
var services = new ServiceCollection();
// Register job
services.AddTransient<MyJob>();
// Register job dependencies
services.AddTransient<IFoo, Foo>();
var container = services.BuildServiceProvider();

// Create an instance of the job factory
var jobFactory = new JobFactory(container);

// Create a Quartz.NET scheduler
var schedulerFactory = new StdSchedulerFactory(properties);
var scheduler = schedulerFactory.GetScheduler();

// Tell the scheduler to use the custom job factory
scheduler.JobFactory = jobFactory;

这个实现已经在一个.NET Core 2.1控制台应用程序中进行了测试,只有一个作业,运行良好。 欢迎留下您的反馈或改进建议...


请解释如何使用它,我正在尝试 var jobFactory = new JobFactory(serviceProvider); 然后在 IJob 的构造函数中调用注入,但是出现了 Quartz.SchedulerException - Tomasz Kowalczyk
job = scope.ServiceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob; 会抛出一个关于找不到已注册服务的异常:No service for type 'xxx.yyy.TestJob' has been registered. 请问您有可用的工作注册示例吗? - Seany84
@Seany84 你需要在 DI 容器中注册你的作业,例如 services.AddTransient<YourJob>();。我已经更新了答案并提供了一个示例。 - CodeZombie
@CodeZombie,我在这里提出了一个问题,如果你感兴趣的话 :) ? - Seany84

21
我知道这是一个老问题,但我想增加一个2020年的答案:
我发现这种方法比不使用.NET Core DI的方法更容易。
在我的项目中,我必须将其集成,然而,Autofac与MS DI一起使用,并且它对某些依赖关系抱怨,因此我还必须添加以下映射:
https://www.quartz-scheduler.net/documentation/quartz-3.x/packages/microsoft-di-integration.html
https://www.quartz-scheduler.net/documentation/quartz-3.x/packages/aspnet-core-integration.html
services.AddSingleton<ITypeLoadHelper, SimpleTypeLoadHelper>();

对我来说,总体解决方案如下:

services.AddTransient<UpcomingReleasesNotificationJob>();
services.AddSingleton<ITypeLoadHelper, SimpleTypeLoadHelper>();

var jobKey = new JobKey("notificationJob");
services.AddQuartz(q =>
{
   q.SchedulerId = "JobScheduler";
   q.SchedulerName = "Job Scheduler";
   q.UseMicrosoftDependencyInjectionScopedJobFactory();
   q.AddJob<UpcomingReleasesNotificationJob>(j => j.WithIdentity(jobKey));
   q.AddTrigger(t => t
      .WithIdentity("notificationJobTrigger")
      .ForJob(jobKey)
      .StartNow()
      .WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(14, 00))
   );
});

services.AddQuartzServer(options =>
{
   options.WaitForJobsToComplete = true;
});

1
如果您想在WorkerService中运行它: A. 安装Quartz.AspNetCore包 B. 添加到ConfigureServices: services.AddQuartzServer(q => { q.WaitForJobsToComplete = true; });更多信息:https://www.quartz-scheduler.net/documentation/quartz-3.x/packages/aspnet-core-integration.html祝使用愉快! - Aharon Ohayon
@Daniel - 在这个版本和方法中如何使所有触发器暂停或待机?我们需要使用计划程序对象对此进行操作,对吧? - Sanjeevi Subramani

2
使用 UseMicrosoftDependencyInjectionJobFactory() 可以使用 DI 创建作业。
        services.AddQuartz(
            q =>
            {
                q.UseMicrosoftDependencyInjectionJobFactory();
                q.ScheduleJob<JobWithDI>(trigger => {});
            }

        services.AddQuartzHostedService();

2
我不确定这个回答是否有帮助,但我为Quartz创建了自己的DI扩展,您可以尝试使用:https://github.com/JaronrH/Quartz.DependencyInjection 简而言之,您可以使用AddQuartz()方法传入[可选] NameValueCollection配置和[必需] Scrutor程序集搜索(请参见https://andrewlock.net/using-scrutor-to-automatically-register-your-services-with-the-asp-net-core-di-container/)。例如:
services.AddQuartz(s => s.FromAssemblyOf<Program>())

这个调用将会:
  • 在程序集(Scrutor)中查找并自动注册所有 IJob、IAddScheduledJob、IAddSchedulerListener、IAddTriggerListener 和 IAddJobListener 实现。所以,是的,你可以这样在 IJob 类中使用 DI!
  • 在 DI 中设置一个 IScheduler 单例,使用上述 DI 资源。
  • 注册一个适配器到 IScheduler,以便 Quartz 的日志记录使用 Microsoft 的 Logging。

然后,你可以使用 provider.StartQuartz() 来启动 Scheduler(如果可用,它会自动查找 IApplicationLifetime 并为 Shutdown 注册 Scheduler),或者使用传统的 DI 获取和启动服务(provider.GetService().Start();)。

希望这能帮到你!


0
最近我遇到了一个问题,即在没有现有范围的情况下解决作业依赖关系。我在作业工厂内部(像正确答案中实现的那样)遇到了一个异常,说“IServiceProvider已被处理”,因为该作业被安排在响应返回给客户端后执行。
因此,我稍微改变了@CodeZombie答案中IJobFactory的实现方式,并且它起作用了。在使用IServiceScopeFactory而不是IServiceProvider的情况下,它与众不同-这确保每次创建新的范围。
public class CommonJobFactory : IJobFactory
    {
        private readonly ILogger<CommonJobFactory> _logger;
        private readonly IServiceScopeFactory _serviceScopeFactory;
        private readonly ConcurrentDictionary<IJob, IServiceScope> _scopes = new ConcurrentDictionary<IJob, IServiceScope>();

        public CommonJobFactory(
            IServiceScopeFactory scopeFactory, 
            ILogger<CommonJobFactory> logger)
        {
            _serviceScopeFactory = scopeFactory;
            _logger = logger;
        }

        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            var newScope = _serviceScopeFactory.CreateScope();
            try
            {
                if (newScope.ServiceProvider.GetService(bundle.JobDetail.JobType) is IJob job && _scopes.TryAdd(job, newScope))
                   return job;

                throw new NullReferenceException("Unable to create new job from scope");
            }
            catch (Exception ex)
            {
                newScope.Dispose();
                _logger.LogError(ex, "Error while constructing {jobType} from scope", bundle.JobDetail.JobType);
                throw;
            }
        }

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

            if (_scopes.TryRemove(job, out var scope))
                scope.Dispose();
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接