using Cronos; namespace ApiPolo.Services { /// public abstract class CronJobService : IHostedService, IDisposable { private System.Timers.Timer? _timer; private readonly CronExpression _expression; private readonly TimeZoneInfo _timeZoneInfo; /// protected CronJobService(string cronExpression, TimeZoneInfo timeZoneInfo) { _expression = CronExpression.Parse(cronExpression); _timeZoneInfo = timeZoneInfo; } /// public virtual async Task StartAsync(CancellationToken cancellationToken) { await ScheduleJob(cancellationToken); } /// protected virtual async Task ScheduleJob(CancellationToken cancellationToken) { var next = _expression.GetNextOccurrence(DateTimeOffset.Now, _timeZoneInfo); if (next.HasValue) { var delay = next.Value - DateTimeOffset.Now; if (delay.TotalMilliseconds <= 0) // prevent non-positive values from being passed into Timer { await ScheduleJob(cancellationToken); } _timer = new System.Timers.Timer(delay.TotalMilliseconds); _timer.Elapsed += async (_, _) => { _timer.Dispose(); // reset and dispose timer _timer = null; if (!cancellationToken.IsCancellationRequested) { await DoWork(cancellationToken); } if (!cancellationToken.IsCancellationRequested) { await ScheduleJob(cancellationToken); // reschedule next } }; _timer.Start(); } await Task.CompletedTask; } /// public virtual async Task DoWork(CancellationToken cancellationToken) { await Task.Delay(5000, cancellationToken); // do the work } /// public virtual async Task StopAsync(CancellationToken cancellationToken) { _timer?.Stop(); await Task.CompletedTask; } /// public virtual void Dispose() { _timer?.Dispose(); GC.SuppressFinalize(this); } } /// public interface IScheduleConfig { string CronExpression { get; set; } TimeZoneInfo TimeZoneInfo { get; set; } } /// public class ScheduleConfig : IScheduleConfig { public string CronExpression { get; set; } = string.Empty; public TimeZoneInfo TimeZoneInfo { get; set; } = TimeZoneInfo.Local; } /// public static class ScheduledServiceExtensions { public static IServiceCollection AddCronJob(this IServiceCollection services, Action> options) where T : CronJobService { if (options == null) { throw new ArgumentNullException(nameof(options), @"Please provide Schedule Configurations."); } var config = new ScheduleConfig(); options.Invoke(config); if (string.IsNullOrWhiteSpace(config.CronExpression)) { throw new ArgumentNullException(nameof(ScheduleConfig.CronExpression), @"Empty Cron Expression is not allowed."); } services.AddSingleton>(config); services.AddHostedService(); return services; } } }