Come pianificare i processi utilizzando Quartz.NET in ASP.NET Core

Quando si lavora su applicazioni Web, è spesso necessario eseguire determinate attività in background. In alcuni casi, si tratta di attività che dovrebbero essere eseguite a intervalli di tempo predefiniti.

Quartz.NET è una porta .NET open source del popolare framework di pianificazione dei lavori Java. È in uso da molto tempo e fornisce un eccellente supporto per lavorare con le espressioni Cron. Puoi saperne di più su Quartz.NET da un post precedente qui. 

Questo articolo presenta una discussione su come lavorare con Quartz.NET in ASP.NET Core per pianificare processi in background.

Per lavorare con gli esempi di codice forniti in questo articolo, dovresti avere Visual Studio 2019 installato nel tuo sistema. Se non hai già una copia, puoi scaricare Visual Studio 2019 qui. 

Crea un progetto API ASP.NET Core

Prima di tutto, creiamo un progetto ASP.NET Core in Visual Studio. Supponendo che Visual Studio 2019 sia installato nel sistema, seguire i passaggi descritti di seguito per creare un nuovo progetto ASP.NET Core in Visual Studio.

  1. Avvia l'IDE di Visual Studio.
  2. Fai clic su "Crea nuovo progetto".
  3. Nella finestra "Crea nuovo progetto", seleziona "Applicazione Web ASP.NET Core" dall'elenco dei modelli visualizzati.
  4. Fare clic su Avanti.
  5. Nella finestra "Configura il tuo nuovo progetto" mostrata di seguito, specifica il nome e la posizione per il nuovo progetto.
  6. Fare clic su Crea.
  7. Nella finestra "Crea nuova applicazione Web ASP.NET Core", seleziona .NET Core come runtime e ASP.NET Core 2.2 (o successivo) dall'elenco a discesa in alto. Userò ASP.NET Core 3.0 qui.
  8. Seleziona "API" come modello di progetto per creare una nuova applicazione API ASP.NET Core. 
  9. Assicurati che le caselle di controllo "Abilita supporto Docker" e "Configura per HTTPS" siano deselezionate poiché non utilizzeremo queste funzionalità qui.
  10. Assicurati che l'autenticazione sia impostata su "Nessuna autenticazione" poiché non utilizzeremo nemmeno l'autenticazione.
  11. Fare clic su Crea. 

Questo creerà un nuovo progetto API ASP.NET Core in Visual Studio. Selezionare la cartella della soluzione Controller nella finestra Esplora soluzioni e fare clic su "Aggiungi -> Controller ..." per creare un nuovo controller denominato DefaultController.

Successivamente, per lavorare con Quartz, è necessario installare il pacchetto Quartz da NuGet. Puoi farlo tramite il gestore di pacchetti NuGet all'interno dell'IDE di Visual Studio 2019 o eseguendo il comando seguente nella console del gestore di pacchetti NuGet:

Pacchetto di installazione Quartz

Processi, trigger e pianificatori Quartz.NET 

I tre concetti principali in Quartz.NET sono lavori, trigger e scheduler. Un lavoro contiene il codice per eseguire un'attività o un lavoro da eseguire. Un lavoro è rappresentato da una classe che implementa l'interfaccia IJob. Un trigger viene utilizzato per specificare la pianificazione e altri dettagli di un lavoro. È possibile sfruttare un trigger per specificare come deve essere eseguito il lavoro. Lo scheduler è il componente responsabile del polling e dell'esecuzione dei lavori in base a pianificazioni predefinite.

Crea uno scheduler usando Quartz.NET

Va notato che è possibile avere più utilità di pianificazione in un'applicazione. Tuttavia, qui utilizzeremo solo uno scheduler per motivi di semplicità. Il frammento di codice seguente illustra come creare un'istanza dello scheduler.

var scheduler = StdSchedulerFactory.GetDefaultScheduler (). GetAwaiter (). GetResult ();

Dopo aver creato lo scheduler, è possibile utilizzare il codice seguente nel metodo ConfigureServices del file Startup.cs per aggiungere l'istanza dello scheduler come servizio singleton.

services.AddSingleton (scheduler);

Avvia e interrompi uno scheduler utilizzando Quartz.NET

Per avviare e fermare lo scheduler ci avvaleremo di un servizio di hosting. Per fare ciò, è necessario creare una classe che implementa l'interfaccia IHostingService come mostrato nello snippet di codice riportato di seguito.

classe pubblica CustomQuartzHostedService: IHostedService

{

        private readonly IScheduler _scheduler;

        public CustomQuartzHostedService (IScheduler scheduler)

        {

            _scheduler = scheduler;

        }

        public async Task StartAsync (CancellationToken cancelToken)

        {

            wait _scheduler? .Start (cancelToken);

        }

        public async Task StopAsync (CancellationToken cancelToken)

        {

            attende _scheduler? .Shutdown (cancelToken);

        }

 }

Si noti che è necessario registrare il servizio ospitato nella raccolta di servizi nel metodo ConfigureServices utilizzando lo snippet di codice fornito di seguito.

services.AddHostedService ();

Ecco il metodo ConfigureServices aggiornato come riferimento:

public void ConfigureServices (servizi IServiceCollection)

{

    services.AddControllers ();

    var scheduler =

    StdSchedulerFactory.GetDefaultScheduler (). GetAwaiter (). GetResult ();

    services.AddSingleton (scheduler);

    services.AddHostedService ();

}

Crea un lavoro utilizzando Quartz.NET

Come ho detto prima, un lavoro è una classe che implementa l'interfaccia IJob e contiene il metodo Execute (). Il metodo Execute () accetta un'istanza di tipo IJobExecutionContext.

Il frammento di codice seguente illustra una classe di lavoro che contiene anche un metodo Execute () asincrono. Questo metodo contiene il codice che corrisponde all'attività che il lavoro deve eseguire.

[DisallowConcurrentExecution]

public class NotificationJob: IJob

    {

        privato in sola lettura ILogger _logger;

        NotificationJob pubblico (logger ILogger)

        {

            _logger = logger;

        }

        public Task Execute (contesto IJobExecutionContext)

        {

            _logger.LogInformation ("Hello world!");

            return Task.CompletedTask;

        }

    }

Crea una fabbrica di lavoro utilizzando Quartz.NET

Una job factory è una classe che eredita l'interfaccia IJobFactory e implementa i metodi NewJob () e ReturnJob (). Il seguente frammento di codice può essere utilizzato per creare una classe factory che può creare e restituire un'istanza di lavoro.

classe pubblica CustomQuartzJobFactory: IJobFactory

    {

        privato in sola lettura IServiceProvider _serviceProvider;

        public CustomQuartzJobFactory (IServiceProvider serviceProvider)

        {

            _serviceProvider = serviceProvider;

        }

        public IJob NewJob (TriggerFiredBundle triggerFiredBundle,

        Scheduler IScheduler)

        {

            var jobDetail = triggerFiredBundle.JobDetail;

            return (IJob) _serviceProvider.GetService (jobDetail.JobType);

        }

        public void ReturnJob (lavoro IJob) {}

    }

Notare che questa implementazione non si avvale del pool di lavori. Se si desidera utilizzare il pool di lavori, è necessario modificare il metodo NewJob () e quindi implementare il metodo ReturnJob ().

Crea una classe JobMetadata per memorizzare i metadati del tuo lavoro

Useremo una classe personalizzata per memorizzare i metadati relativi a un lavoro, ad esempio, l'ID del lavoro, il nome, ecc. La classe seguente rappresenta la classe dei metadati del lavoro.

classe pubblica JobMetadata

    {

        public Guid JobId {get; impostato; }

        public Type JobType {get; }

        stringa pubblica JobName {get; }

        stringa pubblica CronExpression {get; }

        public JobMetadata (Guid Id, Type jobType, string jobName,

        stringa cronExpression)

        {

            JobId = Id;

            JobType = jobType;

            JobName = jobName;

            CronExpression = cronExpression;

        }

    }

Crea un servizio ospitato per avviare e arrestare lo scheduler Quartz.NET

Successivamente, dovremo implementare un servizio ospitato. Un servizio ospitato è una classe che implementa l'interfaccia IHostedService e avvia lo scheduler Quartz. L'elenco di codice seguente illustra una classe di servizio ospitata personalizzata.

classe pubblica CustomQuartzHostedService: IHostedService

    {

        privato in sola lettura ISchedulerFactory schedulerFactory;

        privato in sola lettura IJobFactory jobFactory;

        privato in sola lettura JobMetadata jobMetadata;

        public CustomQuartzHostedService (ISchedulerFactory

            schedulerFactory,

            JobMetadata jobMetadata,

            IJobFactory jobFactory)

        {

            this.schedulerFactory = schedulerFactory;

            this.jobMetadata = jobMetadata;

            this.jobFactory = jobFactory;

        }

        public IScheduler Scheduler {get; impostato; }

        public async Task StartAsync (CancellationToken cancelToken)

        {

            Scheduler = attende schedulerFactory.GetScheduler ();

            Scheduler.JobFactory = jobFactory;

            var job = CreateJob (jobMetadata);

            var trigger = CreateTrigger (jobMetadata);

            attende Scheduler.ScheduleJob (job, trigger, cancelToken);

            attende Scheduler.Start (cancelToken);

        }

        public async Task StopAsync (CancellationToken cancelToken)

        {

            attende Scheduler? .Shutdown (cancelToken);

        }

        privato ITrigger CreateTrigger (JobMetadata jobMetadata)

        {

            return TriggerBuilder.Create ()

            .WithIdentity (jobMetadata.JobId.ToString ())

            .WithCronSchedule (jobMetadata.CronExpression)

            .WithDescription ($ "{jobMetadata.JobName}")

            .Costruire();

        }

        private IJobDetail CreateJob (JobMetadata jobMetadata)

        {

            return JobBuilder

            .Create (jobMetadata.JobType)

            .WithIdentity (jobMetadata.JobId.ToString ())

            .WithDescription ($ "{jobMetadata.JobName}")

            .Costruire();

        }

    }

Il frammento di codice seguente mostra il codice completo del metodo ConfigureServices della classe Startup.

public void ConfigureServices (servizi IServiceCollection)

{

services.AddControllers ();

services.AddSingleton ();

services.AddSingleton ();

services.AddSingleton ();

services.AddSingleton (new JobMetadata (Guid.NewGuid (), typeof (NotificationJob), "Notification Job", "0/10 * * * *?"));

services.AddHostedService ();

}

Ed è tutto quello che devi fare! Quando esegui l'applicazione, noterai che il metodo Execute () della classe NotificationJob viene eseguito una volta ogni 10 secondi.

Quartz.NET è una buona scelta per implementare gli scheduler nelle tue applicazioni. Puoi sfruttare la funzionalità di persistenza in Quartz.NET per archiviare i tuoi lavori in un database come SQL Server, PostgreSQL o SQLite.