Quartz в ASP.NET Core

от автора

Вступление

Знаю, что на эту тему есть очень много статей и своего рода туториоалов, я уже и не говорю об официальной документации, но при работе над своим последним проектом я столкнулся с очень занятной проблемой, о которой мало где говорится. Речь сегодня пойдет о проблеме использования Dependency Injection и Quartz в проекте на платформе ASP.NET Core.

Началось всё с того, что я не думал, что могут возникнуть какие-то проблемы и скажу сразу, что пробовал использовать различные подходы: добавлял все классы, которые включал в себя Quartz в services и юзать их через DI — мимо (но не полностью, как потом оказалось), пробовал добавить HostedService — тоже не работало (в конце прикреплю несколько хороших ссылок на полезные статьи о работе с Quartz) и так далее. Я уже думал, что у меня проблема с триггером — тоже нет. В этой короткой статье я попытаюсь помочь тем, у кого, возможно, была такая же проблема и надеюсь мое решение поможет им в дальнейшей работе. Под конец вступления хочу добавить, что буду весьма признателен если в комментариях те, кто хорошо знаком с технологией, дадут несколько советов, которые помогут улучшить то, что я предложил.

Quartz

Создадим проект (или возьмём готовый — неважно) и добавим в него две папки и несколько классов:

Quartz
—DataJob.cs
—DataScheduler.cs
—JobFactory.cs
Workers
—EmailSender
—IEmailSender

В интерфейсе IEmailSender, который будет служить примером, создадим один метод для отправки писем на почту:

public interface IEmailSender     {         Task SendEmailAsync(string email, string subject, string message);     } 

Теперь опишем класс, который будет реализовывать этот интерфейс:

public class EmailSender : IEmailSender     {                  public Task SendEmailAsync(string email, string subject, string message) 		{ 			var from = "****@gmail.com"; 			var pass = "****";             SmtpClient client = new SmtpClient("smtp.gmail.com", 587); 			client.DeliveryMethod = SmtpDeliveryMethod.Network; 			client.UseDefaultCredentials = false; 			client.Credentials = new System.Net.NetworkCredential(from, pass); 			client.EnableSsl = true; 			var mail = new MailMessage(from, email); 			mail.Subject = subject; 			mail.Body = message; 			mail.IsBodyHtml = true; 			return client.SendMailAsync(mail); 		}     } 

Теперь опишем классы DataJob.cs, DataScheduler.cs, JobFactory.cs. Класс DataJob будет реализовывать интерфейс IJob.

 public class DataJob : IJob     {         private readonly IServiceScopeFactory serviceScopeFactory;          public DataJob(IServiceScopeFactory serviceScopeFactory)         {             this.serviceScopeFactory = serviceScopeFactory;         }          public async Task Execute(IJobExecutionContext context)         {             using (var scope = serviceScopeFactory.CreateScope())             {                 var emailsender = scope.ServiceProvider.GetService<IEmailSender>();                                   await emailsender.SendEmailAsync("example@gmail.com","example","hello")             }         }     } 

Как видим у нас поле типа IServiceScopeFactory, отдда мы будем доставать сервисы напрямую из Startup. Именно этот подход помог решить мне мою проблему, идём далее и опишем клас DataScheduler в котором будем в Sheduler самого кварца добавлять job и trigger:

public static class DataScheduler     {                  public static async void Start(IServiceProvider serviceProvider)         {             IScheduler scheduler = await StdSchedulerFactory.GetDefaultScheduler();             scheduler.JobFactory = serviceProvider.GetService<JobFactory>();             await scheduler.Start();              IJobDetail jobDetail = JobBuilder.Create<DataJob>().Build();             ITrigger trigger = TriggerBuilder.Create()                 .WithIdentity("MailingTrigger", "default")                 .StartNow()                 .WithSimpleSchedule(x => x                 .WithIntervalInMinutes(1)                 .RepeatForever())                 .Build();              await scheduler.ScheduleJob(jobDetail, trigger);         } 

И теперь клас JobFactory, который реализовывает интерфейс IJobFactory:

 public class JobFactory : IJobFactory     {         protected readonly IServiceScopeFactory serviceScopeFactory;                   public JobFactory(IServiceScopeFactory serviceScopeFactory)         {             this.serviceScopeFactory = serviceScopeFactory;         }          public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)         {             using (var scope = serviceScopeFactory.CreateScope())             {                 var job = scope.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob;                 return job;             }                      }          public void ReturnJob(IJob job)         {            //Do something if need         }     } 

Как видим, я, фактически, все зависимости получаю сразу напрямую из serviceScopeFactory. Всё почти готово, осталось изменить класс Program:

public class Program     {         public static void Main(string[] args)         {             var host = BuildWebHost(args);             using (var scope = host.Services.CreateScope())             {                 var serviceProvider = scope.ServiceProvider;                 try                 {                     DataScheduler.Start(serviceProvider);                 }                 catch (Exception)                 {                     throw;                 }             }             host.Run();         }          public static IWebHost BuildWebHost(string[] args) =>            WebHost.CreateDefaultBuilder(args)                .UseStartup<Startup>()                .Build();     } 

И добавить в Startup в метод ConfigureServices следующее:

   services.AddTransient<JobFactory>();    services.AddScoped<DataJob>();    services.AddScoped<IEmailSender,EmailSender>(); 

Готово. Теперь при запуске приложение мы создаем задачу, которая будет срабатывать каждую минуту. Значение можно поменять в DataScheduler.Start (также можно указывать в секундах, часах или использовать CRON). Для каждой новой задачи при таком подходе нужно создавать новый клас, который будет реализовывать IJob и прописывать новую задачу DataScheduler. Также можно и создавать отдельный Scheduler клас для новой задачи.

Буду очень рад, если смог кому-то помочь, а вот пару полезных статей о Quartz и его использовании:

Creating a Quartz.NET hosted service with ASP.NET Core
Using scoped services inside a Quartz.NET hosted service with ASP.NET Core

ссылка на оригинал статьи https://habr.com/ru/post/486678/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *