- Change HealthCheckHtmlGenerator to use DateTime.Now instead of DateTime.UtcNow
- Change ProfileWorker health check timestamps to use DateTime.Now
- Change Program health check endpoint to use DateTime.Now
- Ensures consistent local time usage across the application
- Change from FirstOrDefaultAsync to ToListAsync + FirstOrDefault
- Ensures proper query execution for both EF6 (.NET Framework 4.8) and EF Core (.NET 8.0)
- Prevents potential query execution issues
- Add using System.Linq directive
JobExceptionHandlingBehavior:
- Add ILogger for diagnostic output
- Change ResultText to include full exception details (ToString())
- Log JobException with warning level including ProfileId, JobName, ProcessName, BatchId
- Return default instead of re-throwing to allow graceful handling
ReCRequestExecutionBehavior:
- Convert to primary constructor pattern
- Add ILogger for request tracking
- Store RecActionResult in command for later use
- Log successful ReC requests with detailed metrics (TotalActionCount, ActionExceptionCount)
- Improve error handling and logging
- Add SectionName constant to DexJobOptions
- Update placeholder pattern to {#INT#BATCH_ID}
- Add DexJob configuration section to appsettings.json
- Add IConfiguration parameter to DependencyInjection for options binding
- Add Microsoft.Extensions.Options.ConfigurationExtensions package for .NET Framework 4.8
- Improve code documentation and move class into namespace
Introduced `JobExceptionHandlingBehavior` to handle exceptions, log errors, and rethrow them during MediatR pipeline execution. Updated `DependencyInjection.cs` to register the new behavior and added a `recClientApiUrl` parameter for API configuration.
Enhanced `ProfileMappingProfile.cs` and `GetProfileQuery.cs` with XML documentation for better readability. Improved case-insensitive filtering in `GetProfileQuery` with conditional compilation for .NET version compatibility.
Modified `CreateProfileHistoryCommand.cs` to use a non-nullable `AddedWho` property. Added missing `using` directive in `GetProfileQuery.cs` for compatibility. These changes improve code quality, maintainability, and functionality.
Refactored `JobException` and derived classes (`InactiveProfileException`, `JobHttpException`, `JobSqlException`) to include `profileId` as a required parameter in their constructors. This ensures consistent context for exceptions related to job execution.
Updated behaviors (`CheckQueryExecutionBehavior`, `MainQueryExecutionBehavior`, `ReCRequestExecutionBehavior`) to use the new constructors, passing `profileId` where applicable. Improved exception messages for better debugging context.
Simplified property initialization and enhanced XML documentation for clarity.
Introduce `CreateProfileHistoryCommand` to handle the creation of profile execution history records, including properties for `ProfileId`, `Result`, `ResultText`, and `AddedWho`.
Add `CreateProfileHistoryCommandHandler` to process the command and persist the data using `IProfileHistoryRepository`.
Define AutoMapper mappings in `MappingProfiles` to map `CreateProfileHistoryCommand` to the `ProfileHistory` entity, with specific configurations for ignored and mapped properties.
Removed `SaveChangesAsync` from `IRepository` and `Repository` to centralize transaction management in `DbContext`. Updated `CfgProfileRepository` to use `Context` property instead of `_context`. Refactored `Repository` class to use C# 9.0 record-like constructor syntax and replaced private fields with properties (`Context`, `DbSet`, `Mapper`).
Replaced `Any()` with `Count == 0` for null checks and updated `FindAsync` to use C# 11 object array syntax. Added conditional compilation for framework-specific differences. These changes improve readability, consistency, and leverage modern C# features.
Renamed namespaces, classes, and commands from `DEXJob` to `ProfileJob` to align with the new "Profiles" context. Updated pipeline behaviors (`CheckQueryExecutionBehavior`, `MainQueryExecutionBehavior`, `ReCRequestExecutionBehavior`) to handle `TriggeringProfileJobCommand`.
Refactored unit tests to reflect the new naming convention, including mock setups and assertions. Updated `DtoExtensions` to return `TriggeringProfileJobBatchCommand`. Adjusted queries and dependency injection to use the new `Profiles` namespace.
Performed general refactoring to replace all references to "DEXJob" with "ProfileJob" in method names, variables, and documentation for consistency and clarity.
Updated namespaces for `CheckQueryExecutionBehavior`,
`MainQueryExecutionBehavior`, and `ReCRequestExecutionBehavior`
from `ECMJobRunner.Application.Behaviors` to
`ECMJobRunner.Application.DEXJob.Commands.Behaviors` to better
align with the `DEXJob.Commands` context.
Updated `DependencyInjection.cs` and test files to reference the
new namespace. Added `using Microsoft.Extensions.Options;` to
support the Options pattern in the updated files.
Added two new solution folders, `src` and `test`, to the solution
file (`ECM.JobRunner.sln`) to improve project organization.
Nested existing projects under these folders:
- Projects `{CB94ADEF-59FE-4D7A-83EF-2D57CD325B8F}`,
`{0DC84EFF-0002-4A40-ADDE-D3FE3778D6AA}`,
`{D97CD489-10D7-432C-9921-196F2E0505FF}`, and
`{C60BC965-D293-EA64-B153-1941F0648DF4}` were moved under `src`.
- Project `{F64352B6-32BB-4BDE-90FD-FB77482D44E0}` was moved under `test`.
Introduced a `NestedProjects` section in the solution file to define
the hierarchy. No changes were made to existing project configurations
or build settings.
Introduced a new `ResultType` enum to represent job execution
result types, including `Ok`, `Error`, `Warning`, and `Unknown`.
Added a `Result` property in the `ProfileHistory` class as a
wrapper around the `ResultId` property, with logic to map
`ResultId` to `ResultType` values. Updated `ProfileHistory.cs`
to include necessary namespaces.
Renamed the `Message` method to `CreateMessage` for clarity and updated its usage in the `JobException` constructor. Simplified the error message format by removing visual separator lines, resulting in cleaner and more concise output.
Updated `DependencyInjection.cs` to register AutoMapper with all profiles from the assembly, improving object mapping setup.
Modified `appsettings.json` to update the connection string for `SDD-VMP04-SQL17`, including a new server name and a more secure password, reflecting a move to a different environment or configuration.
Modified the `HealthCheckHtmlGenerator` class to update the
query parameter name in the URLs for the CSS and JavaScript
files. Changed `?{CacheId}` to `?cache={CacheId}` for both
`health-ui.css` and `health-ui.js` to ensure consistency and
improve clarity or compatibility.
Introduced a `CacheId` field in `HealthCheckHtmlGenerator` to append unique query strings to CSS and JS file references. This ensures updated assets are fetched by bypassing browser cache after application restarts. Removed static asset references in favor of dynamic ones.
Updated the `HealthCheckHtmlGenerator` class to change the HTML auto-refresh countdown display from 10 seconds to 3 seconds. Adjusted the `countdown` variable in `health-ui.js` to initialize with 3 seconds, ensuring consistency between the frontend logic and the displayed countdown timer.
Refactor the `ProfileWorker` class to enhance health-check handling, including support for an uninitialized state. The `_lastSuccessfulRun` field was changed to a nullable `DateTime?` to represent when the service has not yet completed its first successful run.
Introduce a new health state, **Degraded (Initializing)**, to indicate the service is starting up. Update the `CheckHealthAsync` method to handle this state and return a `HealthCheckResult.Degraded` with relevant metadata.
Refine health-check logic to safely access `_lastSuccessfulRun.Value` only when initialized. Update documentation to clarify health status conditions and retain adaptive multiplier logic for detecting issues based on interval length.
Enhance `HealthCheckResult` metadata with additional details, including `LastSuccessfulRun`, time since last success, and consecutive failures. Improve comments and descriptions for better clarity.
Replaced the fixed 3x interval multiplier with an adaptive threshold
based on the configured interval duration. Health states (`Healthy`,
`Degraded`, `Unhealthy`) were redefined to reflect this change.
- Adaptive multiplier: 3x for fast intervals (<10s), 1.5x for slower
intervals (≥10s), ensuring faster problem detection for longer
intervals and tolerance for jitter in shorter intervals.
- Updated `CheckHealthAsync` to calculate `maxAllowedDelay` using the
adaptive multiplier.
- Added `Multiplier` and `MaxAllowedDelay` to diagnostic data for
better monitoring and debugging.
- Improved documentation and comments to explain the new logic.
Added detailed XML documentation across multiple files to enhance
code maintainability and readability. Key updates include:
- Documented `AllowAllDashboardAuthorizationFilter` to clarify
its development-only usage.
- Added comments to `DtoExtensions` for Hangfire job ID generation
and DTO-to-command conversion methods.
- Enhanced `HealthCheckHtmlGenerator` with detailed descriptions
of HTML generation methods and utility functions.
- Documented `DependencyInjection` and `ProfileWorkerOptionsValidator`
to explain service registration and configuration validation.
- Updated `ProfileCache` with comments on thread-safe operations.
- Added documentation to `ProfileWork` for profile synchronization
logic and execution flow.
- Enhanced `ProfileWorker` with health check logic and background
service execution details.
- Documented `ProfileWorkerOptions` configuration properties.
These changes aim to improve developer understanding and ensure
best practices are followed in production environments.
Increased the `IntervalMS` value in the `ProfileWorker` section of `appsettings.json` from 1000ms (1 second) to 60000ms (60 seconds). This change reduces the execution frequency of the `ProfileWorker` process, likely to optimize performance or reduce resource usage.
Updated the `Authorization` property in `app.UseHangfireDashboard`
to use modern C# collection initialization syntax (square brackets)
instead of the older array initialization syntax (curly braces).
This change ensures consistency with modern C# conventions.
Replaced `<TargetFramework>` with `<TargetFrameworks>` for future multi-targeting support. Added `<GenerateDocumentationFile>` to enable XML documentation generation. Included metadata properties such as `<PackageId>`, `<Authors>`, `<Company>`, and versioning details to enhance package identity. Added `<PackageTags>` for discoverability and `<UserSecretsId>` for secure development secrets management.
Centralized health check HTML generation into a new static
`HealthCheckHtmlGenerator` class to improve modularity and
maintainability. This class encapsulates the logic for generating
HTML, including navigation, overall status, summary cards,
individual checks, and refresh information.
Replaced the inline `GenerateHealthCheckHtml` method in `Program.cs`
with the new `HealthCheckHtmlGenerator.Generate` method, removing
redundant code and improving separation of concerns. Updated
`Program.cs` to include the necessary namespace.
Simplified the `/health-ui` endpoint to use the new utility class,
reducing code duplication and improving readability.
- Added `app.UseStaticFiles()` to serve static files from `wwwroot`.
- Changed root path redirection to `/health-ui`.
- Moved inline CSS/JS from `Program.cs` to `health-ui.css` and `health-ui.js`.
- Updated navigation links in the health check HTML.
- Enhanced Health UI with improved styles and animations.
- Introduced countdown auto-refresh in `health-ui.js`.
- Updated `launchSettings.json` to start at the root URL.
Introduced a `/health-ui` route with a custom HTML-based health check UI, leveraging `HealthCheckService`. Added `GenerateHealthCheckHtml` to dynamically render health check results with Bootstrap styling, auto-refresh, and detailed status reporting.
Enabled Serilog self-diagnostics and integrated `app.UseSerilogUi()` for a Serilog log dashboard. Enhanced user experience with navigation links, animations, and responsive design. Improved observability and monitoring capabilities.
- ProfileWorker:IntervalMS = 1000 (1 second sync interval)
- Configurable via appsettings.json with IOptions pattern
- Validated at startup via ProfileWorkerOptionsValidator
- Replace direct AddHostedService<ProfileManager> with AddProfileWorker()
- Enables IOptions configuration and validation at startup
- Add using directive for ECMJobRunner.WebCron.ProfileWorker namespace
- ProfileWork.cs: Business logic for DB-to-Hangfire sync
- Fetches active profiles from database via MediatR
- Registers/updates Hangfire recurring jobs with local timezone
- Removes stale jobs (deleted from DB)
- O(n) performance optimization using HashSet for lookups
- Uses ProfileWorker's stopping token for graceful shutdown
Job lifecycle:
- ProfileWorker stops → All running jobs cancelled via token closure
- Jobs use RecurringJobOptions with TimeZoneInfo.Local
- Automatic retry support via Hangfire [AutomaticRetry] attribute
- Create ProfileWorker namespace with 4 separate components
- ProfileWorker.cs: BackgroundService orchestrator with IOptions support
- ProfileWorkerOptions.cs: Configuration model with validation
- ProfileCache.cs: Thread-safe cache using composition pattern
- DependencyInjection.cs: Service registration with IValidateOptions
Benefits:
- Separation of concerns (orchestration, config, state, DI)
- IOptions pattern for appsettings.json configuration
- Startup validation for configuration errors
- Better testability and maintainability
- Map root path (/) to redirect to /hangfire dashboard
- Improves user experience by providing direct access to main dashboard
- Users accessing the application root will be automatically redirected to Hangfire
- Move LogDirectory from Logging section to Application section in appsettings.json
- Update Program.cs to read from Application:LogDirectory instead of Logging:LogDirectory
- Fix JSON schema validation warning for non-standard Logging properties
- Application section now holds custom application-specific settings
- Standard Logging and Serilog sections remain clean and schema-compliant
- Add Serilog.Sinks.SQLite v7.0.0 for structured logging to SQLite database
- Add Serilog.UI v3.2.0 and Serilog.UI.SqliteProvider v1.1.0 for web-based log viewer
- Add Serilog.Settings.Configuration v10.0.1 for configuration support
- Configure SQLite log storage at E:\LogFiles\Digital Data\ECMJobRunner.WebCron\logs.db
- Add Serilog.UI dashboard at /serilog-ui endpoint
- Centralize log directory configuration in appsettings.json (Logging:LogDirectory)
- Configure Serilog programmatically with Console and SQLite sinks
- Remove deprecated Serilog configuration from appsettings files (now using code-based config)
- Enable Serilog self-diagnostics for troubleshooting
- Both Serilog sink and Serilog.UI use same SQLite database path from configuration
- 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
- Add Microsoft.Extensions.Hosting.WindowsServices package
- Add HostingOptions:UseWindowsService configuration flag to appsettings.json
- Configure Program.cs to optionally run as Windows Service based on config
- IIS hosting continues to work by default (UseWindowsService=false)
- Delete obsolete Worker.cs that was conflicting with ProfileManager
- Replace IMediator constructor injection with IServiceScopeFactory
- Create new scope in ExecuteAsync loop to resolve scoped services (ISQLExecutor)
- Fixes: Cannot resolve scoped service from root provider error
- Enables ProfileManager to properly execute MediatR queries with scoped dependencies
- Add CronSchedule property to CfgProfileDto for scheduling support
- Create ProfileSqlJobDto for SQL job execution data transfer
- Update TriggeringDEXJobBatchCommand to accept ProfileId and Jobs collection
- Make TriggeringDEXJobBatchCommand public for Hangfire job activation
- Update Application DependencyInjection with required service registrations
- Create authorization filter to bypass Hangfire dashboard authentication
- Allow unrestricted access to /hangfire dashboard for local development
- Note: Should be replaced with proper authentication in production
- Create ProfileManager background service that polls database every 1 second
- Query active profiles with cron schedules via MediatR GetProfileQuery
- Auto-create/update Hangfire recurring jobs using IRecurringJobManager
- Jobs execute TriggeringDEXJobBatchCommand via IMediator dependency injection
- Add profile caching with schedule change detection to avoid redundant updates
- Create DtoExtensions with JobId() and ToJob() helper methods for profile-to-command conversion
- Install Hangfire packages (Core, AspNetCore, SqlServer, InMemory)
- Configure Hangfire in Program.cs with boolean InMemory flag from appsettings
- Add Hangfire dashboard at /hangfire route
- Update appsettings.json with Hangfire:InMemory configuration
- Update launchSettings.json with new launch URL
- Remove obsolete Worker.cs background service
- Create InactiveProfileException for profile active status validation
- Update TriggeringDEXJobBatchCommand to check profile active status before execution
- Add ICfgProfileRepository dependency to batch command handler
- Throw InactiveProfileException when attempting to execute inactive profiles
- Add profile not found validation with descriptive error message
- Reorganize commands under DEXJob/Commands directory structure
- Add AutoMapper profile for CfgProfile and ProfileSqlJob entity-to-DTO mappings
- Consolidate GetProfileByIdQuery and GetAllActiveProfilesQuery into unified GetProfileQuery
- Implement flexible filtering with nullable query options (Id, Active, TypeId, ProfileName)
- Add IncludeSqlJobs option for optimized SQL job loading
- Move CfgProfileDto to Common/Dtos for better architecture alignment
- Add comprehensive unit tests for GetProfileQuery with multiple filter scenarios
- Move ISQLExecutor interface from Domain to Application layer
- Add GetByIdWithSqlJobsAsync and GetAllActiveWithSqlJobsAsync to repository
- Add TriggeringDEXJobBatchCommandTests
- Test batch command execution with multiple profiles
- Verify batch ID generation and propagation
- Mock ISQLExecutor and IRecClient dependencies
- Add MainQueryExecutionBehaviorTests
- Test main query execution success path
- Test SQL exception handling (JobSqlException)
- Verify MainQueryResults population
- Add CheckQueryExecutionBehaviorTests
- Test check query validation (ErrorAction.SkipInsert)
- Test skip behavior for zero results
- Test SQL exception handling
- All tests pass on both net480 and net8.0 frameworks
- Update ECM.JobRunner.sln with test project reference
- Upgrade Microsoft.Extensions.DependencyInjection to 8.0.1 (ReC.Client requirement)