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