在我的上一篇文章中,我展示了如何使用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. 一些js 概念 整理

    1.原型链 prototype       这个属性 是一个指针,指向一个对象 这个对象 包含 所有实例共享的属性和方法,即这个原型对象是用来给实例共享属性和方法的.          而每个实例内部 ...

  2. pywin32解析office文档

    from win32com.client import Dispatch xlApp = Dispatch("Excel.Application") xlApp.Visible = ...

  3. 不再忍受龟速 Github,你也可以试试在云开发上部署个人博客!

    Hexo 是被大家广泛使用的静态博客系统, 除了在 Github Pages 部署以外,现在你有了一个新的选择,那就是使用云开发静态网站功能来部署啦! 云开发(CloudBase)是一款云端一体化的产 ...

  4. TensorFlow系列专题(九):常用RNN网络结构及依赖优化问题

    欢迎大家关注我们的网站和系列教程:http://panchuang.net/ ,学习更多的机器学习.深度学习的知识! 目录: 常用的循环神经网络结构 多层循环神经网络 双向循环神经网络 递归神经网络 ...

  5. 百道Python面试题实现,搞定Python编程就靠它

    对于一般的机器学习求职者而言,最基础的就是掌握 Python 编程技巧,随后才是相关算法或知识点的掌握.在这篇文章中,我们将介绍一个 Python 练习题项目,它从算法练习题到机试实战题提供了众多问题 ...

  6. Kubernetes(K8s) 安装(使用kubeadm安装Kubernetes集群)

    背景: 由于工作发生了一些变动,很长时间没有写博客了. 概述: 这篇文章是为了介绍使用kubeadm安装Kubernetes集群(可以用于生产级别).使用了Centos 7系统. 一.Centos7 ...

  7. ||,&&短路规则测试

    短路规则:a||b中若a为真,则直接判断整个表达式为真,不再判断b是真或假,    a&&b中若a为假,则直接判断整个表达式为假,不再单独判断b是真或假. 想要测试这个规则的话,可以将 ...

  8. SpringCloud微服务架构和SOA架构

    1,传统的三层架构 在传统的架构中,SSH,SSM,主要分为web 控制层,业务逻辑层,数据库访问层,单点项目,项目没有拆分,所有的开发任务全部写在一个项目中,耦合度比价高,如果程序中的一个功能出现了 ...

  9. lvs + keepalived + gninx 高性能负载均衡

    1,nginx 作为负载均衡器,在应用层实现了负载均衡和反向代理,在分布式集群中,能够有效的去处理大数据量,高访问的应用.但是,如果nginx 服务挂了怎么办? 为此,可以实现两台nginx或者多台n ...

  10. 看完这篇 Session、Cookie、Token,和面试官扯皮就没问题了

    Cookie 和 Session HTTP 协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录:Session 和 Cookie 的主要目的 ...