Chronosix.Hangfire 0.1.0

dotnet add package Chronosix.Hangfire --version 0.1.0
                    
NuGet\Install-Package Chronosix.Hangfire -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Chronosix.Hangfire" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Chronosix.Hangfire" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Chronosix.Hangfire" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Chronosix.Hangfire --version 0.1.0
                    
#r "nuget: Chronosix.Hangfire, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Chronosix.Hangfire@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Chronosix.Hangfire&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Chronosix.Hangfire&version=0.1.0
                    
Install as a Cake Tool

Chronosix

In-process scheduled task & background worker dashboard for ASP.NET Core.

Chronosix is a middleware library that intercepts the state of your background jobs in-process and mounts an embedded dashboard at /chronosix — no database, no separate service, no extra infrastructure. Add one line, press F5, and you can see exactly what your schedulers are doing.

builder.Services.AddChronosix();
// ...
app.MapChronosix();

The problem

Most enterprise .NET apps schedule background work with Quartz.NET, Hangfire, or native IHostedService / BackgroundService loops driven by cron expressions. Checking whether those jobs are actually running correctly usually means staring at rolling console logs or writing throwaway API endpoints just to trigger a job for testing. Hangfire ships a dashboard, but it requires a database back-end to function.

Chronosix removes that friction. It keeps a live, in-memory picture of every job and serves it from your own process.

What you get

  • The "Trigger Now" button — fire any automated job manually, on demand, regardless of its cron schedule. No more waiting until 3am to test the nightly cleanup.
  • Timeline visualizer — a Gantt-style execution graph showing how long each run took and where bottlenecks or overlapping executions occurred, updated in real time over SignalR.
  • Failure tracer — unhandled exceptions thrown inside background worker loops are captured into an in-memory queue with full stack traces, so a failure is something you inspect rather than something you scroll past.
  • Executive dashboard — a refined UI with first-class dark and light themes (toggle in the header, remembered per browser).
  • Zero dependencies — the core library needs nothing beyond the ASP.NET Core shared framework. No database, no Redis, no NuGet dependency tree.

Install

dotnet add package Chronosix

Optional scheduler adapters:

dotnet add package Chronosix.Quartz     # surface Quartz.NET jobs
dotnet add package Chronosix.Hangfire   # surface Hangfire jobs

Targets net8.0, net9.0 and net10.0.

Quick start

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddChronosix();

// A job on a 6-field cron schedule (sec min hour day month day-of-week):
builder.Services.AddChronosixJob<ReportJob>("0 */5 * * * *");

// An instrumented background worker:
builder.Services.AddChronosixWorker<ImportWorker>();

var app = builder.Build();

app.MapChronosix();   // mounts /chronosix, /chronosix/hub and /chronosix/api

app.Run();

Browse to /chronosix.

Authoring jobs

IChronosixJob — scheduler-driven jobs

Implement IChronosixJob and let the built-in scheduler run it. The job is resolved from a fresh DI scope for every execution, so scoped dependencies are safe to use.

public sealed class ReportJob : IChronosixJob
{
    private readonly IReportService _reports;

    public ReportJob(IReportService reports) => _reports = reports;

    public async Task ExecuteAsync(ChronosixJobContext context, CancellationToken ct)
    {
        context.Log("Generating the daily report…");
        await _reports.GenerateAsync(ct);
        context.Log("Done.");
    }
}

Register it on a cron schedule, a fixed interval, or as manual-only:

builder.Services.AddChronosixJob<ReportJob>("0 0 6 * * *");          // cron: 06:00 daily
builder.Services.AddChronosixJob<PollJob>(TimeSpan.FromSeconds(30)); // every 30 seconds
builder.Services.AddChronosixJob<ReindexJob>();                      // manual-only (Trigger Now)

ChronosixBackgroundService — instrumented workers

For a long-lived worker, derive from ChronosixBackgroundService instead of BackgroundService. Chronosix then owns the scheduling loop, times every iteration, captures exceptions and gives the worker a working "Trigger Now" button:

[ChronosixJob(Name = "Import Worker", IntervalSeconds = 25)]
public sealed class ImportWorker : ChronosixBackgroundService
{
    public ImportWorker(IServiceProvider services) : base(services) { }

    protected override async Task ExecuteJobAsync(ChronosixJobContext context, CancellationToken ct)
    {
        context.Log("Importing batch…");
        await DoImportAsync(ct);
    }
}
builder.Services.AddChronosixWorker<ImportWorker>();

If Chronosix is ever not registered, the worker still runs — just uninstrumented — so it is always safe to ship.

Existing BackgroundService workers — no restructuring

Already have native IHostedService / BackgroundService workers — perhaps driven by Cronos expressions — that you would rather not rewrite? Leave the base class and the loop exactly as they are. Inject ChronosixDiagnostics and wrap each iteration in TrackAsync:

public sealed class InventorySyncWorker : BackgroundService
{
    private readonly ChronosixDiagnostics _chronosix;

    public InventorySyncWorker(ChronosixDiagnostics chronosix) => _chronosix = chronosix;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await _chronosix.TrackAsync("InventorySync", SyncAsync, stoppingToken);
            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }

    private async Task SyncAsync(CancellationToken ct) { /* your existing work */ }
}

TrackAsync times the iteration, places it on the timeline, and routes any unhandled exception to the failure tracer without rethrowing it, so the loop keeps running. The worker appears on the dashboard as a background worker. This path gives you the timeline and the failure tracer; it does not give a working "Trigger Now" button — that requires Chronosix to own the loop, so use ChronosixBackgroundService for any worker you want to fire on demand.

Because Chronosix's cron parser accepts the same standard 5- and 6-field syntax as Cronos, cron expression strings you already use with Cronos drop straight into AddChronosixJob<T>("…") unchanged.

Configuration

builder.Services.AddChronosix(options =>
{
    options.DashboardTitle          = "Acme Jobs";
    options.DefaultTheme            = "dark";           // "dark" or "light"
    options.RestrictToDevelopment   = true;            // 404 the dashboard outside Development
    options.SchedulerTimeZone       = TimeZoneInfo.Utc;
    options.MaxExecutionHistory     = 250;             // timeline ring-buffer size
    options.MaxFailureHistory       = 100;             // failure tracer queue size
    options.MaxLogLinesPerExecution = 200;

    // Full custom gate (overrides RestrictToDevelopment):
    options.Authorize = http => http.User.IsInRole("Operations");
});

By default the dashboard is reachable only in the Development environment. To expose it elsewhere, supply an Authorize delegate.

Cron syntax

Chronosix ships its own dependency-free cron parser supporting 5-field (min hour day month day-of-week) and 6-field (leading second) expressions, with *, ?, ranges, steps, lists, wrap-around ranges, and three-letter month/day names. Quartz-only tokens (L, W, #) are not supported — use the Quartz adapter for those.

REST API

The dashboard is driven by a small JSON API under /chronosix/api, which you can also call yourself:

Method & route Purpose
GET /chronosix/api/snapshot Full dashboard snapshot
GET /chronosix/api/jobs/{id}/executions Recent executions for a job
POST /chronosix/api/jobs/{id}/trigger Trigger a job now
POST /chronosix/api/jobs/{id}/enabled?value= Enable / disable a schedule
POST /chronosix/api/failures/{id}/acknowledge Acknowledge a captured failure
POST /chronosix/api/failures/clear Clear the failure queue

Scheduler adapters

Already using Quartz.NET or Hangfire? The adapters surface those jobs on the same dashboard without rewriting them.

// Quartz.NET (call after services.AddQuartz(...)):
builder.Services.AddChronosixQuartz();

// Hangfire (call after services.AddHangfire(...)):
builder.Services.AddChronosixHangfire();

Quartz executions and Hangfire server executions then appear on the timeline, and their exceptions feed the failure tracer. The adapters target Quartz.NET 3.x and Hangfire 1.8.x.

Architecture

┌──────────────────────────────────────────────────────────────┐
│ Your ASP.NET Core process                                      │
│                                                                │
│  IChronosixJob ─┐                                              │
│  Workers ───────┼─▶ ChronosixRunner ─▶ ExecutionTracker ──┐    │
│  Quartz/Hangfire┘        │              FailureTracer ─────┤    │
│   (via adapters)         │              JobRegistry ───────┤    │
│                          ▼                                 ▼    │
│                   DashboardNotifier ─▶ SignalR ─▶ /chronosix    │
└──────────────────────────────────────────────────────────────┘

Everything lives in memory inside your process. Nothing is persisted; restart the app and the dashboard starts fresh.

Repository layout

Path Contents
src/Chronosix Core library, dashboard, API, SignalR hub
adapters/Chronosix.Quartz Quartz.NET adapter
adapters/Chronosix.Hangfire Hangfire adapter
samples/Chronosix.Sample.Web Runnable sample web app with demo jobs
.github/workflows CI and release automation

Building from source

Requires the .NET SDK 10.0.300 (pinned in global.json).

dotnet build Chronosix.sln -c Release
dotnet run --project samples/Chronosix.Sample.Web

Then open http://localhost:5179/chronosix.

Releasing

CI builds and verifies every push. To publish a release, push a semver tag:

git tag v0.1.0
git push origin v0.1.0

The release workflow packs Chronosix, Chronosix.Quartz and Chronosix.Hangfire, pushes them to NuGet.org (using the NUGET_API_KEY repository secret) and creates a GitHub Release with the .nupkg files attached.

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 94 5/24/2026