asp.net-core – 如何在ASP.NET Core中启动Quartz?
我有以下课程
public class MyEmailService { public async Task<bool> SendAdminEmails() { ... } public async Task<bool> SendUserEmails() { ... } } public interface IMyEmailService { Task<bool> SendAdminEmails(); Task<bool> SendUserEmails(); } 我已经安装了最新的Quartz 2.4.1 Nuget package,因为我想在我的Web应用程序中使用轻量级调度程序而没有单独的SQL Server数据库. 我需要安排方法 > SendUserEmails每周一至周一17:00,周二17:00和周二运行.周三17:00 在ASP.NET Core中使用Quartz安排这些方法需要什么代码?我还需要知道如何在ASP.NET Core中启动Quartz,因为互联网上的所有代码示例仍然引用以前版本的ASP.NET. 我可以find a code sample用于以前版本的ASP.NET,但我不知道如何在ASP.NET Core中启动Quartz来开始测试. 解决方法
TL; DR(完整答案可以在下面找到)
假设工具:Visual Studio 2017 RTM,.NET Core 1.1,.NET Core SDK 1.0,SQL Server Express 2016 LocalDB. 在Web应用程序.csproj中: <Project Sdk="Microsoft.NET.Sdk.Web"> <!-- .... existing contents .... --> <!-- add the following ItemGroup element,it adds required packages --> <ItemGroup> <PackageReference Include="Quartz" Version="3.0.0-alpha2" /> <PackageReference Include="Quartz.Serialization.Json" Version="3.0.0-alpha2" /> </ItemGroup> </Project> 在Program类中(默认情况下由Visual Studio搭建): public class Program { private static IScheduler _scheduler; // add this field public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .UseApplicationInsights() .Build(); StartScheduler(); // add this line host.Run(); } // add this method private static void StartScheduler() { var properties = new NameValueCollection { // json serialization is the one supported under .NET Core (binary isn't) ["quartz.serializer.type"] = "json",// the following setup of job store is just for example and it didn't change from v2 // according to your usage scenario though,you definitely need // the ADO.NET job store and not the RAMJobStore. ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX,Quartz",["quartz.jobStore.useProperties"] = "false",["quartz.jobStore.dataSource"] = "default",["quartz.jobStore.tablePrefix"] = "QRTZ_",["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate,["quartz.dataSource.default.provider"] = "SqlServer-41",// SqlServer-41 is the new provider for .NET Core ["quartz.dataSource.default.connectionString"] = @"Server=(localdb)MSSQLLocalDB;Database=Quartz;Integrated Security=true" }; var schedulerFactory = new StdSchedulerFactory(properties); _scheduler = schedulerFactory.GetScheduler().Result; _scheduler.Start().Wait(); var userEmailsJob = JobBuilder.Create<SendUserEmailsJob>() .WithIdentity("SendUserEmails") .Build(); var userEmailsTrigger = TriggerBuilder.Create() .WithIdentity("UserEmailsCron") .StartNow() .WithCronSchedule("0 0 17 ? * MON,TUE,WED") .Build(); _scheduler.ScheduleJob(userEmailsJob,userEmailsTrigger).Wait(); var adminEmailsJob = JobBuilder.Create<SendAdminEmailsJob>() .WithIdentity("SendAdminEmails") .Build(); var adminEmailsTrigger = TriggerBuilder.Create() .WithIdentity("AdminEmailsCron") .StartNow() .WithCronSchedule("0 0 9 ? * THU,FRI") .Build(); _scheduler.ScheduleJob(adminEmailsJob,adminEmailsTrigger).Wait(); } } 作业类的示例: public class SendUserEmailsJob : IJob { public Task Execute(IJobExecutionContext context) { // an instance of email service can be obtained in different ways,// e.g. service locator,constructor injection (requires custom job factory) IMyEmailService emailService = new MyEmailService(); // delegate the actual work to email service return emailService.SendUserEmails(); } } 完整答案 Quartz for .NET Core 首先,根据this announcement,你必须使用Quartz的v3,因为它的目标是.NET Core. 目前,只有v3软件包的alpha版本是available on NuGet.看起来团队花了很多精力发布2.5.0,而不是针对.NET Core.尽管如此,在他们的GitHub回购中,主分支已经专注于v3,基本上,open issues for v3 release似乎并不重要,主要是旧的愿望清单项目,恕我直言.由于最近commit activity相当低,我预计v3会在几个月内发布,或者可能是半年 – 但没有人知道. 乔布斯和IIS回收 如果Web应用程序将在IIS下托管,则必须考虑工作进程的回收/卸载行为. ASP.NET Core Web应用程序作为常规.NET Core进程运行,与w3wp.exe分开 – IIS仅用作反向代理.然而,当循环或卸载w3wp.exe的实例时,相关的.NET Core应用程序进程也会发出信号以退出(根据this). Web应用程序也可以在非IIS反向代理(例如NGINX)后面自行托管,但我会假设您使用IIS,并相应地缩小我的答案. 回收/卸载引入的问题在post referenced by @darin-dimitrov中得到了很好的解释: >例如,如果在星期五9:00,该进程已关闭,因为几个小时之前它由于不活动而被IIS卸载 – 在该进程再次启动之前不会发送管理员电子邮件.为避免这种情况,请配置IIS以最小化卸载/重新循环(see this answer). >根据我的经验,上述配置仍然没有100%保证IIS永远不会卸载应用程序.为了100%保证您的进程已启动,您可以设置一个命令,定期向您的应用程序发送请求,从而使其保持活动状态. >回收/卸载主机进程时,必须正常停止作业,以避免数据损坏. 为什么要在Web应用程序中托管预定作业 尽管存在上面列出的问题,我仍然可以想到将这些电子邮件作业托管在Web应用程序中的一个理由.决定只有一种应用程序模型(ASP.NET).这种方法简化了学习曲线,部署程序,生产监控等. 如果您不想引入后端微服务(这是将电子邮件作业移动到的好地方),那么有必要克服IIS回收/卸载行为,并在Web应用程序中运行Quartz. 或许你有其他原因. 持久的工作商店 在您的方案中,作业执行的状态必须保持在进程外.因此,默认RAMJobStore不适合,您必须使用ADO.NET作业存储. 由于您在问题中提到了SQL Server,我将提供SQL Server数据库的示例设置. 如何启动(并正常停止)调度程序 我假设您使用Visual Studio 2017和最新/最新版本的.NET Core工具.我的是.NET Core Runtime 1.1和.NET Core SDK 1.0. 对于数据库设置示例,我将在SQL Server 2016 Express LocalDB中使用名为Quartz的数据库.数据库设置脚本可以是found here. 首先,向Web应用程序.csproj添加必需的包引用(或者在Visual Studio中使用NuGet包管理器GUI执行此操作): <Project Sdk="Microsoft.NET.Sdk.Web"> <!-- .... existing contents .... --> <!-- the following ItemGroup adds required packages --> <ItemGroup> <PackageReference Include="Quartz" Version="3.0.0-alpha2" /> <PackageReference Include="Quartz.Serialization.Json" Version="3.0.0-alpha2" /> </ItemGroup> </Project> 在Migration Guide和V3 Tutorial的帮助下,我们可以弄清楚如何启动和停止调度程序.我更喜欢将它封装在一个单独的类中,让我们将它命名为QuartzStartup. using System; using System.Collections.Specialized; using System.Threading.Tasks; using Quartz; using Quartz.Impl; namespace WebApplication1 { // Responsible for starting and gracefully stopping the scheduler. public class QuartzStartup { private IScheduler _scheduler; // after Start,and until shutdown completes,references the scheduler object // starts the scheduler,defines the jobs and the triggers public void Start() { if (_scheduler != null) { throw new InvalidOperationException("Already started."); } var properties = new NameValueCollection { // json serialization is the one supported under .NET Core (binary isn't) ["quartz.serializer.type"] = "json",// the following setup of job store is just for example and it didn't change from v2 ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX,// SqlServer-41 is the new provider for .NET Core ["quartz.dataSource.default.connectionString"] = @"Server=(localdb)MSSQLLocalDB;Database=Quartz;Integrated Security=true" }; var schedulerFactory = new StdSchedulerFactory(properties); _scheduler = schedulerFactory.GetScheduler().Result; _scheduler.Start().Wait(); var userEmailsJob = JobBuilder.Create<SendUserEmailsJob>() .WithIdentity("SendUserEmails") .Build(); var userEmailsTrigger = TriggerBuilder.Create() .WithIdentity("UserEmailsCron") .StartNow() .WithCronSchedule("0 0 17 ? * MON,WED") .Build(); _scheduler.ScheduleJob(userEmailsJob,userEmailsTrigger).Wait(); var adminEmailsJob = JobBuilder.Create<SendAdminEmailsJob>() .WithIdentity("SendAdminEmails") .Build(); var adminEmailsTrigger = TriggerBuilder.Create() .WithIdentity("AdminEmailsCron") .StartNow() .WithCronSchedule("0 0 9 ? * THU,FRI") .Build(); _scheduler.ScheduleJob(adminEmailsJob,adminEmailsTrigger).Wait(); } // initiates shutdown of the scheduler,and waits until jobs exit gracefully (within allotted timeout) public void Stop() { if (_scheduler == null) { return; } // give running jobs 30 sec (for example) to stop gracefully if (_scheduler.Shutdown(waitForJobsToComplete: true).Wait(30000)) { _scheduler = null; } else { // jobs didn't exit in timely fashion - log a warning... } } } } 注意1.在上面的示例中,SendUserEmailsJob和SendAdminEmailsJob是实现IJob的类. IJob接口与IMyEmailService略有不同,因为它返回void Task而不是Task< bool>.两个作业类都应该将IMyEmailService作为依赖项(可能是构造函数注入). 注意2.对于能够及时退出的长时间运行的作业,在IJob.Execute方法中,它应该观察IJobExecutionContext.CancellationToken的状态.这可能需要在IMyEmailService接口中进行更改,以使其方法接收CancellationToken参数: public interface IMyEmailService { Task<bool> SendAdminEmails(CancellationToken cancellation); Task<bool> SendUserEmails(CancellationToken cancellation); } 何时何地启动和停止调度程序 在ASP.NET Core中,应用程序引导代码驻留在类Program中,就像在控制台应用程序中一样.调用Main方法来创建Web主机,运行它,并等待它退出: public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .UseApplicationInsights() .Build(); host.Run(); } } 最简单的方法就是在Main方法中调用QuartzStartup.Start,就像我在TL中做的那样; DR.但由于我们必须正确处理进程关闭,我更喜欢以更一致的方式挂接启动和关闭代码. 这一行: .UseStartup<Startup>() 指的是一个名为Startup的类,它在Visual Studio中创建新的ASP.NET Core Web Application项目时被搭建. Startup类如下所示: public class Startup { public Startup(IHostingEnvironment env) { // scaffolded code... } public IConfigurationRoot Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // scaffolded code... } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app,IHostingEnvironment env,ILoggerFactory loggerFactory) { // scaffolded code... } } 很明显,应该在Startup类的一个方法中插入对QuartzStartup.Start的调用.问题是,应该挂起QuartzStartup.Stop. 在旧版.NET Framework中,ASP.NET提供了IRegisteredObject接口.根据this post和documentation,在ASP.NET Core中它被IApplicationLifetime取代.答对了.可以通过参数将IApplicationLifetime的实例注入到Startup.Configure方法中. 为了保持一致性,我将QuartzStartup.Start和QuartzStartup.Stop挂钩到IApplicationLifetime: public class Startup { // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure( IApplicationBuilder app,ILoggerFactory loggerFactory,IApplicationLifetime lifetime) // added this parameter { // the following 3 lines hook QuartzStartup into web host lifecycle var quartz = new QuartzStartup(); lifetime.ApplicationStarted.Register(quartz.Start); lifetime.ApplicationStopping.Register(quartz.Stop); // .... original scaffolded code here .... } // ....the rest of the scaffolded members .... } 请注意,我已使用额外的IApplicationLifetime参数扩展了Configure方法的签名.根据documentation,ApplicationStopping将阻止,直到注册的回调完成. 在IIS Express和ASP.NET Core模块上正常关闭 我能够在IIS上观察IApplicationLifetime.ApplicationStopping挂钩的预期行为,并安装了最新的ASP.NET Core模块. IIS Express(与Visual Studio 2017社区RTM一起安装)和具有过时版本的ASP.NET Core模块的IIS都没有始终如一地调用IApplicationLifetime.ApplicationStopping.我相信这是因为this bug修复了. 您可以安装最新版本的ASP.NET Core模块from here.请按照“安装最新的ASP.NET核心模块”部分中的说明进行操作. Quartz vs. FluentScheduler 我还看了一下FluentScheduler,因为它被@Brice Molesti提议作为替代库.我的第一印象是,与Quartz相比,FluentScheduler是一个相当简单且不成熟的解决方案.例如,FluentScheduler不提供作业状态持久性和集群执行等基本功能. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- asp.net – 在中等信任下,在GMail SMTP上发送电子邮件
- 两个ASP.NET站点共享应用程序文件但具有不同的web.config
- 无需写try/catch,也能正常处理异常
- 什么是asp.net mvc中“literal”标签的等价物
- asp.net – 如果我只是在做一个查找表,我应该使用自动生成的
- nuget – 在部署的asp.net mvc解决方案中需要packages.conf
- asp.net – ascx中的Updatepanel刷新了整个控件
- asp.net-mvc – MVC 4:强制ScriptBundle返回Javascript文件
- asp.net – 访问asp:从代码背后的内容
- asp.net-mvc – TryUpdateModel与强类型方法参数
- asp.net mvc多个连接字符串
- 使用System.Net.Mail中的SMTP发送邮件(带附件)
- 存储ASP.NET会话状态时AppFabric Cache的新缓存参
- asp.net-mvc – 用于子操作的MVC OutputCache:它
- asp.net-web-api – 如何在HttpReponseMessage上
- asp.net – 在页面生命周期的PreInit事件中创建动
- asp.net-mvc-3 – MVC2到MVC3 IOC问题
- asp.net LinkBut??ton HyperLink问题
- 最好的TinyMce编辑器图像管理器/文件上传为Asp.n
- asp.net-membership – 用于创建.Net成员资格提供