Quartzo no núcleo do ASP.NET

Introdução


Sei que existem muitos artigos e algum tipo de tutorial sobre esse assunto, nem estou falando de documentação oficial, mas ao trabalhar no meu projeto mais recente me deparei com um problema muito interessante, que não é mencionado muito. Hoje, falaremos sobre o problema do uso de injeção de dependência e quartzo em um projeto ASP.NET Core.

Tudo começou com o fato de eu não achar que algum problema pudesse surgir e eu diria imediatamente que tentei usar várias abordagens: adicionei todas as classes que o Quartz incluiu nos serviços e as usei através do DI - by (mas não completamente, como se viu depois), tentei adicionar o HostedService - também não funcionou (no final, anexarei alguns bons links a artigos úteis sobre como trabalhar com o Quartz) e assim por diante. Eu já pensei que tinha um problema com o gatilho - nenhum deles. Neste breve artigo, tentarei ajudar aqueles que podem ter tido o mesmo problema e espero que minha solução os ajude em seus futuros trabalhos. No final da introdução, quero acrescentar que ficarei muito agradecido se, nos comentários, aqueles que estão familiarizados com a tecnologia derem alguns conselhos que ajudarão a melhorar o que propus.

Quartzo


Crie um projeto (ou pegue um finalizado - não importa) e adicione duas pastas e várias classes:

Quartzo
--DataJob.cs
--DataScheduler.cs
--JobFactory.cs
Trabalhadores
--EmailSender
--IEmailSender

Na interface IEmailSender, que servirá como exemplo , crie um método para enviar cartas para correspondência:

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

Agora descrevemos a classe que implementará esta interface:

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);
		}
    }

Agora descrevemos as classes DataJob.cs, DataScheduler.cs, JobFactory.cs. A classe DataJob implementará a interface 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")
            }
        }
    }

Como vemos um campo do tipo IServiceScopeFactory, obteremos serviços diretamente da Inicialização. Foi essa abordagem que me ajudou a resolver meu problema, vá em frente e descreva a classe DataScheduler na qual adicionaremos job e trigger no Sheduler do próprio quartzo:

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);
        }

E agora a classe JobFactory, que implementa a interface 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
        }
    }

Como você pode ver, eu, de fato, obtenho todas as dependências diretamente do serviceScopeFactory. Tudo está quase pronto, resta mudar a classe 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();
    }

E adicione o seguinte a Inicialização no método ConfigureServices:

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

Feito. Agora, quando você inicia o aplicativo, criamos uma tarefa que será acionada a cada minuto. O valor pode ser alterado em DataScheduler.Start (também pode ser especificado em segundos, horas ou usar CRON). Para cada nova tarefa com essa abordagem, você precisa criar uma nova classe que implementará o IJob e registre uma nova tarefa do DataScheduler. Você também pode criar uma classe Agendadora separada para uma nova tarefa.

Ficaria muito feliz se pudesse ajudar alguém, mas aqui estão alguns artigos úteis sobre o Quartz e seu uso:

Criando um serviço hospedado no Quartz.NET com o ASP.NET Core
Usando serviços com escopo dentro de um serviço hospedado no Quartz.NET com o ASP.NET Core

Source: https://habr.com/ru/post/undefined/


All Articles