Using quartz. Net in ASP. Net core and worker service

Time:2021-6-2

Now there is an official package, quartz. Extensions. Hosting, which uses quartz. Net to run background tasks, so it’s much easier to add quartz. Net to ASP. Net core or worker service.

I’ll show you how to add quartz. Net hosted service to your app, how to create a simple ijob, and how to register it with trigger.

Introduction – what is quartz. Net

Quartz. Net is a fully functional open source job scheduling system, which can be used in the smallest application to large enterprise system.

There are many asp.net users who run background tasks on timers in a reliable and clustered way. Using quartz. Net used in asp.net core supports. Net standar 2.0, so you can easily use it in your application.

Quartz. Net has three main concepts:

  1. job。 This is the background task you want to run.
  2. trigger。 Trigger controls when a job runs, usually triggered according to certain scheduling rules.
  3. scheduler。 It is responsible for coordinating the job and trigger, and executing the job according to the requirements of trigger.

Asp.net core supports running “background tasks” through hosted services. When your asp.net core application starts, the managed service starts and runs in the background during the life cycle of the application. Quartz. Net 3.2.0 introduces direct support for this pattern through quartz. Extensions. Hosting. Quartz.extensions.hosting can be used in both asp.net core applications and “universal host” based worker service.

Although it is possible to create a “timed” background service (for example, running a task every 10 minutes), quartz.net provides a more robust solution. By using Cron   Trigger, you can make sure that tasks only run at specific times of the day (such as 2:30 a.m.), or only on specific days, or any combination of these times. Quartz. Net also allows you to run multiple instances of an application in a cluster, so that only one instance can run a given task at any time.

The quartz. Net hosting service is responsible for the scheduling of quartz. It will run in the background of the application, check for triggers that are executing, and run related jobs if necessary. You need to configure the scheduler, but don’t worry about starting or stopping it. Ihostedservice will manage it for you.

In this article, I’ll show you the basics of creating a quartz. Net job. It is scheduled to run on a timer in the managed service.

Install quartz. Net

Quartz. Net is a nuget package for. Net standar 2.0, so it’s easy to install in your application. For this test, I created a worker service project. You can use dotnet   The add package quartz. Extensions. Hosting command installs the quartz. Net managed package. If you look at the project’s. Csproj, it should look like this:

<Project Sdk="Microsoft.NET.Sdk.Worker">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <UserSecretsId>dotnet-QuartzWorkerService-9D4BFFBE-BE06-4490-AE8B-8AF1466778FD</UserSecretsId>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
    <PackageReference Include="Quartz.Extensions.Hosting" Version="3.2.3" />
  </ItemGroup>
</Project>

This will add a managed service pack to introduce quartz. Net. Next, we need to register the services of quartz. Net and ihostedservice in the application.

Add quartz. Net hosted service

There are two things you need to do to register quartz. Net:

  1. Register the di container service required by quartz. Net.
  2. Sign up for hosting services.

In asp.net core, these two operations are usually performed in the startup. Configureservices () method. Worker   Services does not use the startup class, so we register them in the configureservices method of ihostbuilder in program.cs

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                // Add the required Quartz.NET services
                services.AddQuartz(q =>
                {
                    // Use a Scoped container to create jobs. I'll touch on this later
                    q.UseMicrosoftDependencyInjectionScopedJobFactory();
                });

                // Add the Quartz.NET hosted service

                services.AddQuartzHostedService(
                    q => q.WaitForJobsToComplete = true);

                // other config
            });
}

Here are some interesting points:

  1. Use Microsoft dependency injection scoped jobfactory: it tells quartz.net to register an ijobfactory by creating jobs from the di container. The scoped part of the method means that your job can use scoped services, not just single or transient services.
  2. Waitforjobstocomplete: this setting ensures that when a request is closed, quartz.net waits for the job to end gracefully before exiting.

If you run the application now, you will see the quartz service start and dump a large number of logs to the console:

info: Quartz.Core.SchedulerSignalerImpl[0]
      Initialized Scheduler Signaller of type: Quartz.Core.SchedulerSignalerImpl
info: Quartz.Core.QuartzScheduler[0]
      Quartz Scheduler v.3.2.3.0 created.
info: Quartz.Core.QuartzScheduler[0]
      JobFactory set to: Quartz.Simpl.MicrosoftDependencyInjectionJobFactory
info: Quartz.Simpl.RAMJobStore[0]
      RAMJobStore initialized.
info: Quartz.Core.QuartzScheduler[0]
      Scheduler meta-data: Quartz Scheduler (v3.2.3.0) 'QuartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'Quartz.Core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'Quartz.Simpl.DefaultThreadPool' - with 10 threads.
  Using job-store 'Quartz.Simpl.RAMJobStore' - which does not support persistence. and is not clustered.

info: Quartz.Impl.StdSchedulerFactory[0]
      Quartz scheduler 'QuartzScheduler' initialized
info: Quartz.Impl.StdSchedulerFactory[0]
      Quartz scheduler version: 3.2.3.0
info: Quartz.Core.QuartzScheduler[0]
      Scheduler QuartzScheduler_$_NON_CLUSTERED started.
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down....

At this point, you have quartz running as a hosted service in your application, but there are no jobs to let it run. In the next section, we will create and register a simple job.

Create job

For the actual background work we are scheduling, we will use a “Hello world” implementation, which writes an ilogger < T >. You should implement the interface ijob of quartz.net, which contains an asynchronous execute () method. Note that we use dependency injection here to inject the logger into the constructor.

using Microsoft.Extensions.Logging;
using Quartz;
using System.Threading.Tasks;
[DisallowConcurrentExecution]
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;
    }
}

I also decorated the job with the [disallowcurrent execution] attribute. This property prevents quartz.net from trying to run the same job at the same time.

Now that we have created the job, we need to register it with the trigger in the di container.

Configure job

Quartz.net provides some simple schedules for running jobs, but one of the most common methods is to use quartz.net   Cron expression. Cron expressions allow complex timer scheduling, so you can set rules, such as “trigger every half hour between 8 a.m. and 10 a.m. on the 5th and 20th of every month.”. Be sure to check the sample documentation when using it, because all cron expressions used by different systems are interchangeable.

The following example shows how to register HelloWorld job with trggier that runs every 5 seconds

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostContext, services) =>
        {
            services.AddQuartz(q =>  
            {
                q.UseMicrosoftDependencyInjectionScopedJobFactory();

                // Create a "key" for the job
                var jobKey = new JobKey("HelloWorldJob");

                // Register the job with the DI container
                q.AddJob<HelloWorldJob>(opts => opts.WithIdentity(jobKey));

                // Create a trigger for the job
                q.AddTrigger(opts => opts
                    .ForJob(jobKey) // link to the HelloWorldJob
                    .WithIdentity("HelloWorldJob-trigger") // give the trigger a unique name
                    .WithCronSchedule("0/5 * * * * ?")); // run every 5 seconds

            });
            services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
            // ...
        });

In this code, we:

  1. Create a unique Jobkey for the job. This is used to connect the job with its trggier. There are other ways to connect jobs and trggiers, but I think that’s as good as any other way.
  2. Use addjob < T > to register HelloWorld job. This does two things – it adds HelloWorld job to the di container so that it can be created; It registers jobs internally with quartz.
  3. Add a trigger to run the job every 5 seconds. We use the Jobkey to associate the trigger with a job and provide a unique name for the trigger (not required in this case, but important if you are running quartz in cluster mode). Finally, we set up a cron schedule for trigger to make the job run every 5 seconds.

This realizes the function! There is no need to create a custom ijobfactory and worry about whether it supports scoped services.

The default package handles all these issues for you – you can use scoped services in ijob, and they will be removed when the job is finished.

If you run your application now, you will see the same startup message as before, and then you will see HelloWorld job write to the console every 5 seconds
Using quartz. Net in ASP. Net core and worker service
That’s all you need to start and run, but to my liking, too much has been added to the configureservices method. You are also less likely to want to hard code job scheduling in your application. For example, if you extract it into a configuration, you can use a different schedule in each environment.

At the very least, we want to extract cron scheduling into the configuration. For example, you can add the following to appsettings.json:

{
  "Quartz": {
     "HelloWorldJob": "0/5 * * * * ?"
   }
 }

Then, you can easily override the trigger scheduling of HelloWorld job in different environments.

To facilitate registration, we can create an extension method to encapsulate the registration of ijob on quartz and set its trigger schedule. This code is basically the same as the previous example, but it uses the job name to load the cron schedule in iconfiguration.

public static class ServiceCollectionQuartzConfiguratorExtensions{
    public static void AddJobAndTrigger<T>(
        this IServiceCollectionQuartzConfigurator quartz,
        IConfiguration config)
        where T : IJob
    {
        // Use the name of the IJob as the appsettings.json key
        string jobName = typeof(T).Name;

        // Try and load the schedule from configuration
        var configKey = $"Quartz:{jobName}";
        var cronSchedule = config[configKey];

        // Some minor validation
        if (string.IsNullOrEmpty(cronSchedule))
        {
            throw new Exception($"No Quartz.NET Cron schedule found for job in configuration at {configKey}");
        }

        // register the job as before
        var jobKey = new JobKey(jobName);
        quartz.AddJob<T>(opts => opts.WithIdentity(jobKey));

        quartz.AddTrigger(opts => opts
            .ForJob(jobKey)
            .WithIdentity(jobName + "-trigger")
            .WithCronSchedule(cronSchedule)); // use the schedule from configuration
    }
}

Now we can use the extension method to clean up the application’s program. CS:

public class Program
{
    public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.AddQuartz(q =>
                {
                    q.UseMicrosoftDependencyInjectionScopedJobFactory();

                    // Register the job, loading the schedule from configuration
                    q.AddJobAndTrigger<HelloWorldJob>(hostContext.Configuration);
                });

                services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
            });
}

This is essentially the same as our configuration, but we’ve made it easier to add new jobs and move scheduling details to the configuration.

Running the application again gives the same output: job writes the output every 5 seconds.

Using quartz. Net in ASP. Net core and worker service

summary

In this article, I introduced quartz.net and showed how to easily add an asp.net core hosted service to run quartz scheduler using the new quartz.extensions.hosting. I showed you how to use trigger to implement a simple job and how to register it with an application so that the hosted service can run it on schedule.

 Welcome to official account official account. If you love foreign technical articles, you can recommend it to me through public comments.

Using quartz. Net in ASP. Net core and worker service