Files
ECMJobRunner/ECMJobRunner.WebCron/ProfileManager.cs
TekH 8b0f97c330 feat: Add Hangfire cronjob system with web dashboard, Windows Service support, and Serilog file/console logging
- Add Hangfire packages (AspNetCore, Core, InMemory, SqlServer) with configurable storage (InMemory vs SQL Server)
- Configure Hangfire dashboard at /hangfire with AllowAllDashboardAuthorizationFilter (no auth for development)
- Add Microsoft.Extensions.Hosting.WindowsServices package with conditional UseWindowsService() based on HostingOptions:UseWindowsService config
- Create ProfileManager BackgroundService with IServiceScopeFactory for scoped service resolution per iteration
- Create AllowAllDashboardAuthorizationFilter for Hangfire dashboard access
- Create DtoExtensions with JobId() and ToJob() helper methods
- Configure Serilog with file sink (Production: Logs/log-.txt, daily rolling, 30 day retention) and console sink (Development)
- Add Serilog enrichers: FromLogContext, WithMachineName, WithThreadId
- Update appsettings.json with Hangfire:InMemory flag, HostingOptions:UseWindowsService flag, and Serilog configuration
- Create appsettings.Development.json with console-specific Serilog configuration
2026-07-12 04:49:22 +02:00

72 lines
2.7 KiB
C#

using ECMJobRunner.Application.Common.Dtos;
using ECMJobRunner.Application.DEXJob.Commands;
using ECMJobRunner.Application.DEXJob.Queries;
using ECMJobRunner.WebCron.Extensions;
using Hangfire;
using MediatR;
using System.Collections.Concurrent;
namespace ECMJobRunner.WebCron
{
public class ProfileManager(ILogger<ProfileManager> Logger, IServiceScopeFactory ScopeFactory, IRecurringJobManager JobManager) : BackgroundService
{
private readonly ConcurrentDictionary<string, CfgProfileDto> Profiles = new();
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
while (!stoppingToken.IsCancellationRequested)
{
if (Logger.IsEnabled(LogLevel.Information))
{
Logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
}
// Create a scope to resolve scoped services (ISQLExecutor used by MediatR pipeline)
using var scope = ScopeFactory.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
var profiles = await mediator.Send(new GetProfileQuery()
{
Active = true,
IncludeSqlJobs = true
}, stoppingToken);
foreach (var profile in profiles)
{
if (Profiles.TryGetValue(profile.JobId(), out var currentProfile)
&& currentProfile.Schedule == profile.Schedule)
continue;
// Add or update recurring job using MediatR command
JobManager.AddOrUpdate<IMediator>(
profile.JobId(),
mediator => mediator.Send(profile.ToJob(), CancellationToken.None),
profile.Schedule,
new RecurringJobOptions
{
TimeZone = TimeZoneInfo.Local
}
);
// Store/update in local cache
Profiles[profile.JobId()] = profile;
Logger.LogInformation("Job {JobId} registered with schedule: {Schedule}",
profile.JobId(), profile.Schedule);
}
await Task.Delay(1000, stoppingToken);
}
}
catch (Exception ex)
{
Logger.LogError(ex, "An error occurred in ProfileManager.");
}
}
}
}