I have the following class
public class MyEmailService
{
public async Task<bool> SendAdminEmails()
{
...
}
public async Task<bool> SendUserEmails()
{
...
}
}
public interface IMyEmailService
{
Task<bool> SendAdminEmails();
Task<bool> SendUserEmails();
}
I have installed the latest Quartz 2.4.1 Nuget package as I wanted a lightweight scheduler in my web app without a separate SQL Server database.
I need to schedule the methods
SendUserEmails
to run every week on Mondays 17:00,Tuesdays 17:00 & Wednesdays 17:00SendAdminEmails
to run every week on Thursdays 09:00, Fridays 9:00
What code do I need to schedule these methods using Quartz in ASP.NET Core? I also need to know how to start Quartz in ASP.NET Core as all code samples on the internet still refer to previous versions of ASP.NET.
I can find a code sample for the previous version of ASP.NET but I don't know how to start Quartz in ASP.NET Core to start testing.
Where do I put the JobScheduler.Start();
in ASP.NET Core?
I don't know how to do it with Quartz, but i had experimented the same scenario with an other library wich works very well. Here how I dit it
Install FluentScheduler
Use it like this
Documentation can be found here FluentScheduler on GitHub
In addition to @felix-b answer. Adding DI to jobs. Also QuartzStartup Start can be made async.
Based on this answer: https://stackoverflow.com/a/42158004/1235390
JobFactory class
Startup class:
SampleJob class with contructor dependency injection:
TL;DR (full answer can be found below)
Assumed tooling: Visual Studio 2017 RTM, .NET Core 1.1, .NET Core SDK 1.0, SQL Server Express 2016 LocalDB.
In web application .csproj:
In the
Program
class (as scaffolded by Visual Studio by default):An example of a job class:
Full answer
Quartz for .NET Core
First, you have to use v3 of Quartz, as it targets .NET Core, according to this announcement.
Currently, only alpha versions of v3 packages are available on NuGet. It looks like the team put a lot of effort into releasing 2.5.0, which does not target .NET Core. Nevertheless, in their GitHub repo, the
master
branch is already dedicated to v3, and basically, open issues for v3 release don't seem to be critical, mostly old wishlist items, IMHO. Since recent commit activity is quite low, I would expect v3 release in few months, or maybe half year - but no one knows.Jobs and IIS recycling
If the web application is going to be hosted under IIS, you have to take into consideration recycling/unloading behavior of worker processes. The ASP.NET Core web app runs as a regular .NET Core process, separate from w3wp.exe - IIS only serves as a reverse proxy. Nevertheless, when an instance of w3wp.exe is recycled or unloaded, the related .NET Core app process is also signaled to exit (according to this).
Web application can also be self-hosted behind a non-IIS reverse proxy (e.g. NGINX), but I will assume that you do use IIS, and narrow my answer accordingly.
The problems that recycling/unloading introduces are explained well in the post referenced by @darin-dimitrov:
Why would you host scheduled jobs in a web app
I can think of one justification of having those email jobs hosted in a web app, despite the problems listed above. It is decision to have only one kind of application model (ASP.NET). Such approach simplifies learning curve, deployment procedure, production monitoring, etc.
If you don't want to introduce backend microservices (which would be a good place to move the email jobs to), then it makes sense to overcome IIS recycling/unloading behaviors, and run Quartz inside a web app.
Or maybe you have other reasons.
Persistent job store
In your scenario, status of job execution must be persisted out of process. Therefore, default RAMJobStore doesn't fit, and you have to use the ADO.NET Job Store.
Since you mentioned SQL Server in the question, I will provide example setup for SQL Server database.
How to start (and gracefully stop) the scheduler
I assume you use Visual Studio 2017 and latest/recent version of .NET Core tooling. Mine is .NET Core Runtime 1.1 and .NET Core SDK 1.0.
For DB setup example, I will use a database named
Quartz
in SQL Server 2016 Express LocalDB. DB setup scripts can be found here.First, add required package references to web application .csproj (or do it with NuGet package manager GUI in Visual Studio):
With the help of Migration Guide and the V3 Tutorial, we can figure out how to start and stop the scheduler. I prefer to encapsulate this in a separate class, let's name it
QuartzStartup
.Note 1. In the above example,
SendUserEmailsJob
andSendAdminEmailsJob
are classes that implementIJob
. TheIJob
interface is slightly different fromIMyEmailService
, because it returns voidTask
and notTask<bool>
. Both job classes should getIMyEmailService
as a dependency (probably constructor injection).Note 2. For a long-running job to be able to exit in timely fashion, in the
IJob.Execute
method, it should observe the status ofIJobExecutionContext.CancellationToken
. This may require change inIMyEmailService
interface, to make its methods receiveCancellationToken
parameter:When and where to start and stop the scheduler
In ASP.NET Core, application bootstrap code resides in class
Program
, much like in console app. TheMain
method is called to create web host, run it, and wait until it exits:The simplest thing to do is just put a call to
QuartzStartup.Start
right in theMain
method, much like as I did in TL;DR. But since we have to properly handle process shutdown as well, I prefer to hook both startup and shutdown code in a more consistent manner.This line:
refers to a class named
Startup
, which is scaffolded when creating new ASP.NET Core Web Application project in Visual Studio. TheStartup
class looks like this:It is clear that a call to
QuartzStartup.Start
should be inserted in one of methods in theStartup
class. The question is, whereQuartzStartup.Stop
should be hooked.In the legacy .NET Framework, ASP.NET provided
IRegisteredObject
interface. According to this post, and the documentation, in ASP.NET Core it was replaced withIApplicationLifetime
. Bingo. An instance ofIApplicationLifetime
can be injected intoStartup.Configure
method through a parameter.For consistency, I will hook both
QuartzStartup.Start
andQuartzStartup.Stop
toIApplicationLifetime
:Note that I have extended the signature of the
Configure
method with an additionalIApplicationLifetime
parameter. According to documentation,ApplicationStopping
will block until registered callbacks are completed.Graceful shutdown on IIS Express, and ASP.NET Core module
I was able to observe expected behavior of
IApplicationLifetime.ApplicationStopping
hook only on IIS, with the latest ASP.NET Core module installed. Both IIS Express (installed with Visual Studio 2017 Community RTM), and IIS with an outdated version of ASP.NET Core module didn't consistently invokeIApplicationLifetime.ApplicationStopping
. I believe it is because of this bug that was fixed.You can install latest version of ASP.NET Core module from here. Follow the instructions in the "Installing the latest ASP.NET Core Module" section.
Quartz vs. FluentScheduler
I also took a look at FluentScheduler, as it was proposed as an alternative library by @Brice Molesti. To my first impression, FluentScheduler is quite a simplistic and immature solution, compared to Quartz. For example, FluentScheduler doesn't provide such fundamental features as job status persistence and clustered execution.