Merge pull request #5110 from sbwalker/dev
improve HostedServiceBase so that scheduled jobs can be registered during installation
This commit is contained in:
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -45,110 +46,140 @@ namespace Oqtane.Infrastructure
|
|||||||
|
|
||||||
protected async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
await Task.Yield(); // required so that this method does not block startup
|
|
||||||
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
|
IConfigurationRoot _config = scope.ServiceProvider.GetRequiredService<IConfigurationRoot>();
|
||||||
ILogger<HostedServiceBase> _filelogger = scope.ServiceProvider.GetRequiredService<ILogger<HostedServiceBase>>();
|
ILogger<HostedServiceBase> _filelogger = scope.ServiceProvider.GetRequiredService<ILogger<HostedServiceBase>>();
|
||||||
|
|
||||||
try
|
// if framework is installed
|
||||||
|
if (IsInstalled(_config))
|
||||||
{
|
{
|
||||||
var jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
|
try
|
||||||
var jobLogs = scope.ServiceProvider.GetRequiredService<IJobLogRepository>();
|
|
||||||
var tenantRepository = scope.ServiceProvider.GetRequiredService<ITenantRepository>();
|
|
||||||
var tenantManager = scope.ServiceProvider.GetRequiredService<ITenantManager>();
|
|
||||||
|
|
||||||
// get name of job
|
|
||||||
string jobType = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
|
|
||||||
|
|
||||||
// load jobs and find current job
|
|
||||||
Job job = jobs.GetJobs().Where(item => item.JobType == jobType).FirstOrDefault();
|
|
||||||
if (job != null && job.IsEnabled && !job.IsExecuting)
|
|
||||||
{
|
{
|
||||||
// get next execution date
|
var jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
|
||||||
DateTime NextExecution;
|
|
||||||
if (job.NextExecution == null)
|
// get name of job
|
||||||
|
string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
|
||||||
|
|
||||||
|
// load jobs and find current job
|
||||||
|
Job job = jobs.GetJobs().Where(item => item.JobType == jobTypeName).FirstOrDefault();
|
||||||
|
|
||||||
|
if (job == null)
|
||||||
{
|
{
|
||||||
if (job.StartDate != null)
|
// auto registration
|
||||||
|
job = new Job { JobType = jobTypeName };
|
||||||
|
|
||||||
|
// optional HostedServiceBase properties
|
||||||
|
var jobType = Type.GetType(jobTypeName);
|
||||||
|
var jobObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, jobType) as HostedServiceBase;
|
||||||
|
if (jobObject.Name != "")
|
||||||
{
|
{
|
||||||
NextExecution = job.StartDate.Value;
|
job.Name = jobObject.Name;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NextExecution = DateTime.UtcNow;
|
job.Name = Utilities.GetTypeName(job.JobType);
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
NextExecution = job.NextExecution.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine if the job should be run
|
|
||||||
if (NextExecution <= DateTime.UtcNow && (job.EndDate == null || job.EndDate >= DateTime.UtcNow))
|
|
||||||
{
|
|
||||||
// update the job to indicate it is running
|
|
||||||
job.IsExecuting = true;
|
|
||||||
jobs.UpdateJob(job);
|
|
||||||
|
|
||||||
// create a job log entry
|
|
||||||
JobLog log = new JobLog();
|
|
||||||
log.JobId = job.JobId;
|
|
||||||
log.StartDate = DateTime.UtcNow;
|
|
||||||
log.FinishDate = null;
|
|
||||||
log.Succeeded = false;
|
|
||||||
log.Notes = "";
|
|
||||||
log = jobLogs.AddJobLog(log);
|
|
||||||
|
|
||||||
// execute the job
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var notes = "";
|
|
||||||
foreach (var tenant in tenantRepository.GetTenants())
|
|
||||||
{
|
|
||||||
// set tenant and execute job
|
|
||||||
tenantManager.SetTenant(tenant.TenantId);
|
|
||||||
notes += ExecuteJob(scope.ServiceProvider);
|
|
||||||
notes += await ExecuteJobAsync(scope.ServiceProvider);
|
|
||||||
}
|
|
||||||
log.Notes = notes;
|
|
||||||
log.Succeeded = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
log.Notes = ex.Message;
|
|
||||||
log.Succeeded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the job log
|
|
||||||
log.FinishDate = DateTime.UtcNow;
|
|
||||||
jobLogs.UpdateJobLog(log);
|
|
||||||
|
|
||||||
// update the job
|
|
||||||
job.NextExecution = CalculateNextExecution(NextExecution, job);
|
|
||||||
if (job.Frequency == "O") // one time
|
|
||||||
{
|
|
||||||
job.EndDate = DateTime.UtcNow;
|
|
||||||
job.NextExecution = null;
|
|
||||||
}
|
}
|
||||||
|
job.Frequency = jobObject.Frequency;
|
||||||
|
job.Interval = jobObject.Interval;
|
||||||
|
job.StartDate = jobObject.StartDate;
|
||||||
|
job.EndDate = jobObject.EndDate;
|
||||||
|
job.RetentionHistory = jobObject.RetentionHistory;
|
||||||
|
job.IsEnabled = jobObject.IsEnabled;
|
||||||
|
job.IsStarted = true;
|
||||||
job.IsExecuting = false;
|
job.IsExecuting = false;
|
||||||
jobs.UpdateJob(job);
|
job.NextExecution = null;
|
||||||
|
|
||||||
// trim the job log
|
job = jobs.AddJob(job);
|
||||||
List<JobLog> logs = jobLogs.GetJobLogs().Where(item => item.JobId == job.JobId)
|
}
|
||||||
.OrderByDescending(item => item.JobLogId).ToList();
|
|
||||||
for (int i = logs.Count; i > job.RetentionHistory; i--)
|
if (job != null && job.IsEnabled && !job.IsExecuting)
|
||||||
|
{
|
||||||
|
var jobLogs = scope.ServiceProvider.GetRequiredService<IJobLogRepository>();
|
||||||
|
var tenantRepository = scope.ServiceProvider.GetRequiredService<ITenantRepository>();
|
||||||
|
var tenantManager = scope.ServiceProvider.GetRequiredService<ITenantManager>();
|
||||||
|
|
||||||
|
// get next execution date
|
||||||
|
DateTime NextExecution;
|
||||||
|
if (job.NextExecution == null)
|
||||||
{
|
{
|
||||||
jobLogs.DeleteJobLog(logs[i - 1].JobLogId);
|
if (job.StartDate != null)
|
||||||
|
{
|
||||||
|
NextExecution = job.StartDate.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NextExecution = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NextExecution = job.NextExecution.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine if the job should be run
|
||||||
|
if (NextExecution <= DateTime.UtcNow && (job.EndDate == null || job.EndDate >= DateTime.UtcNow))
|
||||||
|
{
|
||||||
|
// update the job to indicate it is running
|
||||||
|
job.IsExecuting = true;
|
||||||
|
jobs.UpdateJob(job);
|
||||||
|
|
||||||
|
// create a job log entry
|
||||||
|
JobLog log = new JobLog();
|
||||||
|
log.JobId = job.JobId;
|
||||||
|
log.StartDate = DateTime.UtcNow;
|
||||||
|
log.FinishDate = null;
|
||||||
|
log.Succeeded = false;
|
||||||
|
log.Notes = "";
|
||||||
|
log = jobLogs.AddJobLog(log);
|
||||||
|
|
||||||
|
// execute the job
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var notes = "";
|
||||||
|
foreach (var tenant in tenantRepository.GetTenants())
|
||||||
|
{
|
||||||
|
// set tenant and execute job
|
||||||
|
tenantManager.SetTenant(tenant.TenantId);
|
||||||
|
notes += ExecuteJob(scope.ServiceProvider);
|
||||||
|
notes += await ExecuteJobAsync(scope.ServiceProvider);
|
||||||
|
}
|
||||||
|
log.Notes = notes;
|
||||||
|
log.Succeeded = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
log.Notes = ex.Message;
|
||||||
|
log.Succeeded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the job log
|
||||||
|
log.FinishDate = DateTime.UtcNow;
|
||||||
|
jobLogs.UpdateJobLog(log);
|
||||||
|
|
||||||
|
// update the job
|
||||||
|
job.NextExecution = CalculateNextExecution(NextExecution, job);
|
||||||
|
if (job.Frequency == "O") // one time
|
||||||
|
{
|
||||||
|
job.EndDate = DateTime.UtcNow;
|
||||||
|
job.NextExecution = null;
|
||||||
|
}
|
||||||
|
job.IsExecuting = false;
|
||||||
|
jobs.UpdateJob(job);
|
||||||
|
|
||||||
|
// trim the job log
|
||||||
|
List<JobLog> logs = jobLogs.GetJobLogs().Where(item => item.JobId == job.JobId)
|
||||||
|
.OrderByDescending(item => item.JobLogId).ToList();
|
||||||
|
for (int i = logs.Count; i > job.RetentionHistory; i--)
|
||||||
|
{
|
||||||
|
jobLogs.DeleteJobLog(logs[i - 1].JobLogId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// can occur during the initial installation because the database has not yet been created
|
|
||||||
if (!ex.Message.Contains("No database provider has been configured for this DbContext"))
|
|
||||||
{
|
{
|
||||||
_filelogger.LogError(Utilities.LogMessage(this, $"An Error Occurred Executing Scheduled Job: {Name} - {ex}"));
|
_filelogger.LogError(Utilities.LogMessage(this, $"An Error Occurred Executing Scheduled Job: {Name} - {ex}"));
|
||||||
}
|
}
|
||||||
@ -208,55 +239,28 @@ namespace Oqtane.Infrastructure
|
|||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
|
IConfigurationRoot _config = scope.ServiceProvider.GetRequiredService<IConfigurationRoot>();
|
||||||
ILogger<HostedServiceBase> _filelogger = scope.ServiceProvider.GetRequiredService<ILogger<HostedServiceBase>>();
|
ILogger<HostedServiceBase> _filelogger = scope.ServiceProvider.GetRequiredService<ILogger<HostedServiceBase>>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
|
if (IsInstalled(_config))
|
||||||
IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
|
|
||||||
Job job = jobs.GetJobs().Where(item => item.JobType == jobTypeName).FirstOrDefault();
|
|
||||||
if (job != null)
|
|
||||||
{
|
{
|
||||||
// reset in case this job was forcefully terminated previously
|
string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
|
||||||
job.IsStarted = true;
|
IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
|
||||||
job.IsExecuting = false;
|
Job job = jobs.GetJobs().Where(item => item.JobType == jobTypeName).FirstOrDefault();
|
||||||
jobs.UpdateJob(job);
|
if (job != null)
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// auto registration - job will not run on initial installation due to no DBContext but will run after restart
|
|
||||||
job = new Job { JobType = jobTypeName };
|
|
||||||
|
|
||||||
// optional HostedServiceBase properties
|
|
||||||
var jobType = Type.GetType(jobTypeName);
|
|
||||||
var jobObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, jobType) as HostedServiceBase;
|
|
||||||
if (jobObject.Name != "")
|
|
||||||
{
|
{
|
||||||
job.Name = jobObject.Name;
|
// reset in case this job was forcefully terminated previously
|
||||||
|
job.IsStarted = true;
|
||||||
|
job.IsExecuting = false;
|
||||||
|
jobs.UpdateJob(job);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
job.Name = Utilities.GetTypeName(job.JobType);
|
|
||||||
}
|
|
||||||
job.Frequency = jobObject.Frequency;
|
|
||||||
job.Interval = jobObject.Interval;
|
|
||||||
job.StartDate = jobObject.StartDate;
|
|
||||||
job.EndDate = jobObject.EndDate;
|
|
||||||
job.RetentionHistory = jobObject.RetentionHistory;
|
|
||||||
job.IsEnabled = jobObject.IsEnabled;
|
|
||||||
job.IsStarted = true;
|
|
||||||
job.IsExecuting = false;
|
|
||||||
job.NextExecution = null;
|
|
||||||
jobs.AddJob(job);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// can occur during the initial installation because the database has not yet been created
|
_filelogger.LogError(Utilities.LogMessage(this, $"An Error Occurred Starting Scheduled Job: {Name} - {ex}"));
|
||||||
if (!ex.Message.Contains("No database provider has been configured for this DbContext"))
|
|
||||||
{
|
|
||||||
_filelogger.LogError(Utilities.LogMessage(this, $"An Error Occurred Starting Scheduled Job: {Name} - {ex}"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,6 +318,11 @@ namespace Oqtane.Infrastructure
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsInstalled(IConfigurationRoot config)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(config.GetConnectionString(SettingKeys.ConnectionStringKey));
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cancellationTokenSource.Cancel();
|
_cancellationTokenSource.Cancel();
|
||||||
|
Reference in New Issue
Block a user