From f158a222f42f172b74163d2d149842103870f006 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 18 Feb 2025 11:35:38 -0500 Subject: [PATCH] improve HostedServiceBase so that scheduled jobs can be registered during installation --- .../Infrastructure/Jobs/HostedServiceBase.cs | 253 +++++++++--------- 1 file changed, 131 insertions(+), 122 deletions(-) diff --git a/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs b/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs index be8e8c69..5c8fc04f 100644 --- a/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs +++ b/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -45,110 +46,140 @@ namespace Oqtane.Infrastructure protected async Task ExecuteAsync(CancellationToken stoppingToken) { - await Task.Yield(); // required so that this method does not block startup - while (!stoppingToken.IsCancellationRequested) { using (var scope = _serviceScopeFactory.CreateScope()) { + IConfigurationRoot _config = scope.ServiceProvider.GetRequiredService(); ILogger _filelogger = scope.ServiceProvider.GetRequiredService>(); - try + // if framework is installed + if (IsInstalled(_config)) { - var jobs = scope.ServiceProvider.GetRequiredService(); - var jobLogs = scope.ServiceProvider.GetRequiredService(); - var tenantRepository = scope.ServiceProvider.GetRequiredService(); - var tenantManager = scope.ServiceProvider.GetRequiredService(); - - // 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) + try { - // get next execution date - DateTime NextExecution; - if (job.NextExecution == null) + var jobs = scope.ServiceProvider.GetRequiredService(); + + // 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 { - 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.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; - jobs.UpdateJob(job); + job.NextExecution = null; - // trim the job log - List logs = jobLogs.GetJobLogs().Where(item => item.JobId == job.JobId) - .OrderByDescending(item => item.JobLogId).ToList(); - for (int i = logs.Count; i > job.RetentionHistory; i--) + job = jobs.AddJob(job); + } + + if (job != null && job.IsEnabled && !job.IsExecuting) + { + var jobLogs = scope.ServiceProvider.GetRequiredService(); + var tenantRepository = scope.ServiceProvider.GetRequiredService(); + var tenantManager = scope.ServiceProvider.GetRequiredService(); + + // 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 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) - { - // 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")) + catch (Exception 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()) { + IConfigurationRoot _config = scope.ServiceProvider.GetRequiredService(); ILogger _filelogger = scope.ServiceProvider.GetRequiredService>(); try { - string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName); - IJobRepository jobs = scope.ServiceProvider.GetRequiredService(); - Job job = jobs.GetJobs().Where(item => item.JobType == jobTypeName).FirstOrDefault(); - if (job != null) + if (IsInstalled(_config)) { - // reset in case this job was forcefully terminated previously - job.IsStarted = true; - job.IsExecuting = false; - jobs.UpdateJob(job); - } - 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 != "") + string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName); + IJobRepository jobs = scope.ServiceProvider.GetRequiredService(); + Job job = jobs.GetJobs().Where(item => item.JobType == jobTypeName).FirstOrDefault(); + if (job != null) { - 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) { - // 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 Starting Scheduled Job: {Name} - {ex}")); - } + _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() { _cancellationTokenSource.Cancel();