【问题标题】:How to integrate Quartz.Net jobs that require "scoped" services injected in them (ASP.NET Core 2.0)?如何集成需要在其中注入“范围”服务的 Quartz.Net 作业(ASP.NET Core 2.0)?
【发布时间】:2019-03-14 18:56:43
【问题描述】:

我正在尝试在 my own answer 之后从 this question 创建一些 Quartz.Net 工作。但是,如果作业相当复杂并且需要“作用域”(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 作业(ASP.NET Core 2.0)?

【问题讨论】:

  • 对于每个请求对应的Scoped服务,不应该定义为静态属性。尝试将IServiceProvider 注入QuartzJobFactory(IServiceProvider serviceProvider)
  • @TaoZhou - 我的初始代码注入 IServiceProvider,但由于作业被定义为单例(这可能是有道理的,因为我通常想要一个运行的实例)和其他服务为“范围”(我需要这个,因为它们为应用程序的其他部分提供业务逻辑并且必须限定范围)。如果我将作业设为范围,则注入的提供者无法为我提供作业的实例。我编写的代码似乎可以正常工作,我只是不喜欢这种模式(似乎不对)。

标签: asp.net-core asp.net-core-2.0 quartz.net


【解决方案1】:

要从IServiceProvider 重新解析范围服务,请尝试

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

【讨论】:

  • 我已经尝试过了,并且不得不将添加作业更改为作用域而不是单例(由于依赖的作用域服务)。然而,在这样做之后,dbcontext 似乎搞砸了(试图使用已经释放的上下文)。我如何添加它并没有什么奇怪的(services.AddDbContext&lt;CustomApiContext&gt;(opt =&gt; opt.UseSqlServer(Configuration.GetConnectionString("Default")));
  • @Alexei had to change add jobs as scoped 是什么意思?对于这个2: if jobs are scoped, the provider cannot solve the injected services,它表示您被注入为作用域作业。是否有任何演示可以重现您的问题?
  • 我最初的工作注册如下:services.Add(jobs.Select(jobType =&gt; new ServiceDescriptor(jobType, jobType, ServiceLifetime.Singleton)))。由于 MyJob 是单例,因此失败并出现“无法将范围服务 ... 注入 MyJob”之类的错误。有道理,所以我改成了“services.Add(jobs.Select(jobType => new ServiceDescriptor(jobType, jobType, ServiceLifetime.Scoped)));” .现在数据库上下文说我正在尝试访问已释放的上下文,但不清楚下一步该做什么。
  • 您的答案是正确的,我的问题出在某个地方,因为我终于找到了here。谢谢。
【解决方案2】:

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

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

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

【讨论】:

  • 感谢您的更新。与此同时,我已经切换到当时没有任何 Quartz 问题的 HangFire,但无论如何很高兴知道。
  • 你们知道如何在 Quartz 3.3.2 版本中将服务注入作业的示例吗?
猜你喜欢
  • 2021-12-05
  • 1970-01-01
  • 2018-07-22
  • 2018-11-20
  • 1970-01-01
  • 1970-01-01
  • 2019-11-09
  • 1970-01-01
  • 2016-11-05
相关资源
最近更新 更多