在我的上一篇文章中,我展示了如何使用ASP.NET Core创建Quartz.NET托管服务并使用它来按计划运行后台任务。不幸的是,由于Quartz.NET API的工作方式,在Quartz作业中使用Scoped依赖项注入服务有些麻烦。说明下这篇文章部分采用机翻。

作者:依乐祝

译文地址:https://www.cnblogs.com/yilezhu/p/12757411.html

原文地址:https://andrewlock.net/using-scoped-services-inside-a-quartz-net-hosted-service-with-asp-net-core/

在这篇文章中,我将展示一种简化工作中使用Scoped服务的方法。您可以使用相同的方法来管理EF Core的工作单元模式和其他面向切面的模型。

这篇文章是上篇文章引申出来的,因此,如果您还没有阅读的话,建议您先阅读上篇文章。

回顾-自定义JobFactory和单例的IJob

在上篇博客的最后,我们有一个实现了IJob接口并向控制台简单输出信息的HelloWorldJob

public class HelloWorldJob : IJob
{
private readonly ILogger<HelloWorldJob> _logger;
public HelloWorldJob(ILogger<HelloWorldJob> logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Hello world!");
return Task.CompletedTask;
}
}

我们还有一个IJobFactory的实现,以便我们在需要时从DI容器中检索作业的实例:

public class SingletonJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
public SingletonJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
}
public void ReturnJob(IJob job) { }
}

这些服务都在Startup.ConfigureServices()中以单例形式注册:

services.AddSingleton<IJobFactory, SingletonJobFactory>();
services.AddSingleton<HelloWorldJob>();

对于这个非常基本的示例来说,这很好,但是如果您需要在IJob内部使用一些范围服务呢?例如,也许您需要使用EF Core DbContext遍历所有客户,并向他们发送电子邮件,并更新客户记录。我们假设这个任务为EmailReminderJob

权宜之计

我在上一篇文章中展示的解决方案是将IServiceProvider注入到您的IJob的文档中,手动创建一个范围,并从中检索必要的服务。例如:

public class EmailReminderJob : IJob
{
private readonly IServiceProvider _provider;
public EmailReminderJob( IServiceProvider provider)
{
_provider = provider;
}
public Task Execute(IJobExecutionContext context)
{
using(var scope = _provider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<AppDbContext>();
var emailSender = scope.ServiceProvider.GetService<IEmailSender>();
// fetch customers, send email, update DB
}
return Task.CompletedTask;
}
}

在许多情况下,这种方法绝对可以。如果不是将实现直接放在工作内部(如我上面所做的那样),而是使用中介者模式来处理诸如工作单元或消息分发之类的跨领域问题,则尤其如此。

如果不是这种情况,您可能会受益于创建一个可以为您管理这些工作的帮助类。

QuartzJobRunner

要解决这些问题,您可以创建一个IJob的“中间” 实现,这里我们命名为QuartzJobRunner,该实现位于IJobFactory和要运行的IJob之间。我将很快介绍作业实现,但是首先让我们更新现有的IJobFactory实现以无论请求哪个作业,始终返回QuartzJobRunner的实例,:

using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Spi;
using System;
public class JobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
public JobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return _serviceProvider.GetRequiredService<QuartzJobRunner>();
}
public void ReturnJob(IJob job) { }
}

如您所见,该NewJob()方法始终返回QuartzJobRunner的实例。我们将在Startup.ConfigureServices()中将QuartzJobRunner注册为单例模式,因此我们不必担心它没有被明确释放。

services.AddSingleton<QuartzJobRunner>();

我们将在QuartzJobRunner中创建实际所需的IJob实例。QuartzJobRunner中的job会创建范围,实例化IJob的请求并执行它:

using Microsoft.Extensions.DependencyInjection;
using Quartz;
using System;
using System.Threading.Tasks;
public class QuartzJobRunner : IJob
{
private readonly IServiceProvider _serviceProvider;
public QuartzJobRunner(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task Execute(IJobExecutionContext context)
{
using (var scope = _serviceProvider.CreateScope())
{
var jobType = context.JobDetail.JobType;
var job = scope.ServiceProvider.GetRequiredService(jobType) as IJob;
await job.Execute(context);
}
}
}

在这一点上,您可能想知道,通过添加这个额外的间接层,我们获得了什么好处?主要有以下两个主要优点:

  • 我们可以将EmailReminderJob注册为范围服务,并直接将任何依赖项注入其构造函数中
  • 我们可以将其他横切关注点转移到QuartzJobRunner类中。

作业可以直接使用作用域服务

由于作业实例是从IServiceProvder作用域中解析来的,因此您可以在作业实现的构造函数中安全地使用作用域服务。这使的EmailReminderJob的实现更加清晰,并遵循构造函数注入的典型模式。如果您不熟悉DI范围界定问题,则可能很难理解它们,因此任何对您不利的事情在我看来都是一个好主意:

[DisallowConcurrentExecution]
public class EmailReminderJob : IJob
{
private readonly AppDbContext _dbContext;
private readonly IEmailSender _emailSender;
public EmailReminderJob(AppDbContext dbContext, IEmailSender emailSender)
{
_dbContext = dbContext;
_emailSender = emailSender;
}
public Task Execute(IJobExecutionContext context)
{
// fetch customers, send email, update DB
return Task.CompletedTask;
}
}

这些IJob的实现可以使用以下任何生存期(作用域或瞬态)来在Startup.ConfigureServices()中注册(JobSchedule仍然可以是单例):

services.AddScoped<EmailReminderJob>();
services.AddSingleton(new JobSchedule(
jobType: typeof(EmailReminderJob),
cronExpression: "0 0 12 * * ?")); // every day at noon

QuartzJobRunner可以处理横切关注点

QuartzJobRunner处理正在执行的IJob的整个生命周期:它从容器中获取,执行并释放它(在释放范围时)。因此,它很适合处理其他跨领域问题。

例如,假设您有一个需要更新数据库并将事件发送到消息总线的服务。您可以在每个单独的IJob实现中处理所有这些问题,也可以将跨领域的“提交更改”和“调度消息”操作移到QuartzJobRunner中。

这个例子显然是非常基础的。如果这里的代码适合您,我建议您观看吉米·博加德(Jimmy Bogard)的“六小段失败线”演讲,其中描述了一些问题!

public class QuartzJobRunner : IJob
{
private readonly IServiceProvider _serviceProvider;
public QuartzJobRunner(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task Execute(IJobExecutionContext context)
{
using (var scope = _serviceProvider.CreateScope())
{
var jobType = context.JobDetail.JobType;
var job = scope.ServiceProvider.GetRequiredService(jobType) as IJob;
var dbContext = _serviceProvider.GetRequiredService<AppDbContext>();
var messageBus = _serviceProvider.GetRequiredService<IBus>();
await job.Execute(context);
// job completed, save dbContext changes
await dbContext.SaveChangesAsync();
// db transaction succeeded, send messages
await messageBus.DispatchAsync();
}
}
}

这里的QuartzJobRunner实现与上一个非常相似,但是在执行的我们请求的IJob之前,我们从DI容器中解析了DbContext和消息总线服务。当作业成功执行后(即未抛出异常),我们将所有未提交的更改保存在中DbContext,并在消息总线上调度事件。

将这些方法移到QuartzJobRunner中应该可以减少IJob实现中的重复代码,并且可以更容易地移到更正式的管道和其他模式(如果您希望以后这样做的话)。

可替代解决方案

我喜欢本文中显示的方法(使用中间QuartzJobRunner类),主要有两个原因:

  • 您的其他IJob实现不需要任何有关创建作用域的基础结构的知识,只需完成标准构造函数注入即可
  • IJobFactory中不需要做做任何特殊处理工作。该QuartzJobRunner通过创建和处理作用域隐式地处理这个问题。

但是,此处显示的方法并不是在工作中使用范围服务的唯一方法。马修·阿伯特(Matthew Abbot) 在这个文章中演示了一种方法,该方法旨在以正确处理运行后的作业的方式实现IJobFactory。它有点笨拙,因为你必须匹配接口API,但可以说它更接近你应该实现它的方式!我个人认为我会坚持使用这种QuartzJobRunner方法,但是你可以选择最适合您的方法

【半译】在ASP.NET Core中创建内部使用作用域服务的Quartz.NET宿主服务的更多相关文章

  1. 在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度

    在这篇文章中,我将介绍如何使用ASP.NET Core托管服务运行Quartz.NET作业.这样的好处是我们可以在应用程序启动和停止时很方便的来控制我们的Job的运行状态.接下来我将演示如何创建一个简 ...

  2. [译]在Asp.Net Core 中使用外部登陆(google、微博...)

    原文出自Rui Figueiredo的博文<External Login Providers in ASP.NET Core> 摘要:本文主要介绍了使用外部登陆提供程序登陆的流程,以及身份 ...

  3. Asp.Net Core中创建多DbContext并迁移到数据库

    在我们的项目中我们有时候需要在我们的项目中创建DbContext,而且这些DbContext之间有明显的界限,比如系统中两个DbContext一个是和整个数据库的权限相关的内容而另外一个DbConte ...

  4. 在ASP.NET Core中创建自定义端点可视化图

    在上篇文章中,我为构建自定义端点可视化图奠定了基础,正如我在第一篇文章中展示的那样.该图显示了端点路由的不同部分:文字值,参数,动词约束和产生结果的端点: 在本文中,我将展示如何通过创建一个自定义的D ...

  5. ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存

    分享 最近在公司成功落地了一个用ASP.NET Core 开发前台的CMS项目,虽然对于表层的开发是兼容MVC5的,但是作为爱好者当然要用尽量多的ASP.NET Core新功能了. 背景 在项目开发的 ...

  6. Windows IIS ASP.NET Core中创建和使用HTTPS自签名证书

    为什么要用Https就不说了. 第一步:创建自签名的证书.在Windows下开启PowerShell,将以下文字粘贴进去: # setup certificate properties includi ...

  7. Asp.net core中的依赖注入

    使用服务 在Asp.net core的Controller中,可以通过如下两种方式获取系统注入的服务: 构造函数 可以直接在构造函数中传入所依赖的服务,这是非常常见的DI注入方式. public Va ...

  8. ASP.NET Core 中的SEO优化(3):自定义路由匹配和生成

    前言 前两篇文章主要总结了CMS系统两个技术点在ASP.NET Core中的应用: <ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存> <ASP.NET ...

  9. ASP.NET Core 中的SEO优化(2):中间件中渲染Razor视图

    前言 上一篇文章<ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存>中介绍了中间件的使用方法.以及使用中间件实现服务端静态化缓存的功能.本系列文章的这些技巧都是我 ...

随机推荐

  1. Log4Net读取XML配置文件及在代码中完成添加Logger操作

    解决问题: 将log4net配置文件与app.config配置文件分开 手动读取log4net配置文件 手动创建logger 可将日志输出功能封装在类库中,应用程序引用时无需添加assembly引用及 ...

  2. Anomaly Detection-异常检测算法(Coursera-Ng-ML课程)

    现实生活中有许多需要提前预防一些异常问题出现的情况,例如在飞机起飞前,对飞机各部分进行评估,看发动机等各个零件是否性能正常,若有潜在的问题(可能出现异常情况),则需要及时检修或更换. 那么我们如何去评 ...

  3. 开始 Keras 序列模型(Sequential model)

    开始 Keras 序列模型(Sequential model) 序列模型是一个线性的层次堆栈. 你可以通过传递一系列 layer 实例给构造器来创建一个序列模型. The Sequential mod ...

  4. WeChat-SmallProgram:自定义select下拉选项框组件

    1):创建组件所需的文件 2):自定义组件 CSS 及 JS 组件的wxml: <view class='com-selectBox'> <view class='com-sCont ...

  5. MySQL进阶篇(01):基于多个维度,分析服务器性能

    本文源码:GitHub·点这里 || GitEE·点这里 一.服务器性能简介 1.性能定义 服务器性能优化是一项非常艰巨的任务,当然也是很难处理的问题,在写这篇文章的时候,特意请教下运维大佬,硬件工程 ...

  6. ubuntu查看并杀死自己之前运行的进程解决办法RuntimeError: CUDA error: out of memory

    问题描述:在跑深度学习算法的时候,发现服务器上只有自己在使用GPU,但使用GPU总是会报RuntimeError: CUDA error: out of memory,这是因为自己之前运行的进程还存在 ...

  7. 【Ubuntu】常用命令汇总,整理ing

    Ubuntu 常用命令(在此页面中Ctrl+F即可快速查找) 在Ubuntu系统使用过程中,会不断地接触到命令行操作,下面对一些常用的命令进行汇总,方便查找. 1.文件操作 1.1 文件复制拷贝 cp ...

  8. 悟懂MapReduce,不纠结!

    在<谷歌 MapReduce 初探>中,我们通过统计词频的 WordCount 经典案例,对 Google 推出的 MapReduce 编程模型有了一个认识,但是那种认识,还只是停留在知道 ...

  9. 理解 Hanoi 汉诺塔非递归算法

    汉诺塔介绍: 汉诺塔(港台:河内塔)是根据一个传说形成的数学问题: 最早发明这个问题的人是法国数学家爱德华·卢卡斯. 传说越南河内某间寺院有三根银棒,上串 64 个金盘.寺院里的僧侣依照一个古老的预言 ...

  10. Visual Studio2000系列版本安装OpenGL可以这么简单!

    是啥 直接上图 [翻译过来]这个库将各种库添加到您的项目中,这些库在x86和x64架构上构建OpenGL应用程序所必需的.包括FreeGLUT,GLFW和GLEW.也就是说,大家常用的几个OpenGL ...