如何集成 Quartz.Net 个需要注入 "scoped" 服务的作业 (ASP.NET Core 2.0)?

How to integrate Quartz.Net jobs that require "scoped" services injected in them (ASP.NET Core 2.0)?

我正在尝试在 my own answer from this question 之后创建一些 Quartz.Net 个职位。但是,如果作业相当复杂并且需要 "scoped" (services.AddScoped<.., ...>) 项服务,则该示例不起作用,因为作业是作为单例创建的。

如果我将它们更改为范围,则 serviceProvider 不包含我需要的服务。我已经设法使用以下代码使其工作:

Startup.cs

/// <summary>
/// service provider to be used by qiaryz job factory which cannot use its default provider
/// since child services are scoped and the jobs are singleton
/// </summary>
public static IServiceProvider QuartzScopedProvider { get; private set; }

private void ConfigureQuartz(IServiceCollection services, params Type[] jobs)
{
    services.AddSingleton<IJobFactory, QuartzJobFactory>();
    services.Add(jobs.Select(jobType => new ServiceDescriptor(jobType, jobType, ServiceLifetime.Singleton)));

    QuartzScopedProvider = services.BuildServiceProvider();

    services.AddSingleton(provider =>
    {
        var schedulerFactory = new StdSchedulerFactory();
        var scheduler = schedulerFactory.GetScheduler().Result;
        scheduler.JobFactory = provider.GetService<IJobFactory>();
        scheduler.Start();
        return scheduler;
    });
}

/// <summary>
/// configures quartz services
/// </summary>
/// <param name="services"></param>
protected virtual void ConfigureJobsIoc(IServiceCollection services)
{
    // all custom services are already defined at this point

    ConfigureQuartz(services, typeof(ComplexJob));
}

/// <summary>
/// configures and starts async jobs (Quartz)
/// </summary>
/// <param name="app"></param>
/// <param name="lifetime"></param>
protected virtual void StartJobs(IApplicationBuilder app, IApplicationLifetime lifetime)
{
    var scheduler = app.ApplicationServices.GetService<IScheduler>();

    QuartzServicesUtilities.StartJob<ComplexJob>(scheduler, TimeSpan.FromMinutes(60));

    lifetime.ApplicationStarted.Register(() => scheduler.Start());
    lifetime.ApplicationStopping.Register(() => scheduler.Shutdown(waitForJobsToComplete: true));
}

QuartzJobFactory.cs

作业工厂没有使用注入的服务提供者,而是在Startup.cs

中显式构建的服务提供者
public class QuartzJobFactory : IJobFactory
{
    private readonly IServiceProvider _serviceProvider;

    /// <inheritdoc/>
    public QuartzJobFactory()
    {
        // _serviceProvider = serviceProvider;
        _serviceProvider = Startup.QuartzScopedProvider;
    }

    /// <inheritdoc/>
    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        var jobDetail = bundle.JobDetail;

        // this fails with injected service provider:
        // 1: cannot inject scoped services in singleton service
        // 2: if jobs are scoped, the provider cannot solve the injected services
        var job = (IJob)_serviceProvider.GetService(jobDetail.JobType);
        return job;
    }

    /// <inheritdoc/>
    public void ReturnJob(IJob job) { }
}

我想知道这是否是处理 ASP.NET Core 2.0 中 Quartz 作业的好方法,因为它看起来更像是 hack 而不是真正的解决方案。

问题:如何集成 Quartz.Net 需要注入 "scoped" 服务的作业(ASP.NET Core 2.0)?

要从 IServiceProvider 中恢复范围内的服务,请尝试

    using (var scope = _serviceProvider.CreateScope())
    {
        var job = (IJob)scope.ServiceProvider.GetService(jobDetail.JobType);            
    }

从 Quartz 3.1 开始,无需创建自定义 JobFactory(或 JobRunner)来使用作用域服务。我们可以使用内置作业工厂,它已经为我们创建了范围:

services.AddQuartz(q =>  
{                   
    q.UseMicrosoftDependencyInjectionScopedJobFactory();
});

来自docs。 在幕后,MicrosoftDependencyInjectionJobFactory 在作业执行之前创建范围,并使用包装器 ScopedJob 来 运行 范围内的目标作业。