Updated `<FileVersion>` and `<AssemblyVersion>` in the
`ECMJobRunner.WebCron.csproj` file from `1.1.0.0` to `1.1.0.1`.
This reflects a minor revision, likely for a small update or bug fix.
Added a new `IISProfile.pubxml` configuration file to enable
publishing the web application as a package. Configured the
build to use the `Release` configuration and `Any CPU`
platform. Set the target framework to `.NET 8.0` and defined
the IIS deployment path as `JobRunner.WebCron`. The package
is created as a single file and includes the `App_Data`
folder. Specified the desktop build package location and
added a unique project GUID for identification.
Updated versioning details in `ECMJobRunner.WebCron.csproj`:
- `<Version>` updated from `1.0.0` to `1.1.0`.
- `<FileVersion>` updated from `1.0.0.0` to `1.1.0.0`.
- `<AssemblyVersion>` updated from `1.0.0.0` to `1.1.0.0`.
- `<InformationalVersion>` updated from `1.0.0` to `1.1.0`.
Introduced `ExceptionHandlingMiddleware` to handle exceptions
globally in the application. The middleware captures exceptions
thrown during the request pipeline, logs them, and returns
appropriate HTTP responses in JSON format.
Key features:
- Handles `JobException` with a 400 Bad Request response.
- Handles unhandled exceptions with a 500 Internal Server Error.
- Logs warnings for `JobException` and errors for unhandled
exceptions.
- Sets response `ContentType` to `application/json` and writes
error details as JSON.
Added necessary `using` directives for required namespaces.
TriggeringProfileJobCommand:
- Add RecActionResult property to store ReC action execution results
- Convert handler to primary constructor with ISender injection
- Create ProfileHistory after successful job execution
- Include detailed execution metrics in history (TotalActionCount, ActionExceptionCount, BatchId)
- Add required using statements for ProfileHistories and ValueObjects
ProfileWorkerOptionsValidator:
- Fix XML documentation reference to ValidateOptionsResult.Fail(string)
- 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
- 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
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 `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
- 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
Introduced a new .NET 8.0 web application project with a minimal API setup.
Configured Swagger for API documentation and added a `Worker` class as a
background service for periodic tasks.
Added `launchSettings.json` to define development profiles for HTTP, HTTPS,
and IIS Express. Configured logging levels in `appsettings.json` and
`appsettings.Development.json`.
Included `Swashbuckle.AspNetCore` package for Swagger integration and
configured the HTTP request pipeline to support development and production
environments.