Dosaic.Plugins.Jobs.Hangfire 1.2.31

dotnet add package Dosaic.Plugins.Jobs.Hangfire --version 1.2.31
                    
NuGet\Install-Package Dosaic.Plugins.Jobs.Hangfire -Version 1.2.31
                    
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="Dosaic.Plugins.Jobs.Hangfire" Version="1.2.31" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Dosaic.Plugins.Jobs.Hangfire" Version="1.2.31" />
                    
Directory.Packages.props
<PackageReference Include="Dosaic.Plugins.Jobs.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 Dosaic.Plugins.Jobs.Hangfire --version 1.2.31
                    
#r "nuget: Dosaic.Plugins.Jobs.Hangfire, 1.2.31"
                    
#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 Dosaic.Plugins.Jobs.Hangfire@1.2.31
                    
#: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=Dosaic.Plugins.Jobs.Hangfire&version=1.2.31
                    
Install as a Cake Addin
#tool nuget:?package=Dosaic.Plugins.Jobs.Hangfire&version=1.2.31
                    
Install as a Cake Tool

Dosaic.Plugins.Jobs.Hangfire

Dosaic.Plugins.Jobs.Hangfire is a plugin that allows Dosaic-based services to schedule and manage background jobs using Hangfire. It supports recurring (cron) jobs, fire-and-forget jobs, delayed jobs, PostgreSQL or in-memory storage, a built-in dashboard, OpenTelemetry tracing, Prometheus metrics, and feature-flag-based job execution control.

Installation

dotnet add package Dosaic.Plugins.Jobs.Hangfire

or add as a package reference to your .csproj:

<PackageReference Include="Dosaic.Plugins.Jobs.Hangfire" Version="" />

Configuration

Configure appsettings.yml (or appsettings.json) with the hangfire section:

hangfire:
  # PostgreSQL storage (ignored when inMemory: true)
  host: localhost
  port: 5432
  database: postgres
  user: postgres
  password: postgres

  # Use in-memory storage instead of PostgreSQL (useful for development)
  inMemory: true

  # Hostname from which the Hangfire dashboard is accessible.
  # Leave empty to disable dashboard access entirely.
  allowedDashboardHost: localhost

  # Enable Microsoft Feature Management integration to toggle jobs via config
  enableJobsByFeatureManagementConfig: false

  # Additional queues to listen on (the "default" queue is always included)
  queues:
    - default
    - critical

  # Optional tuning
  pollingIntervalInMs: 5000       # defaults to Hangfire built-in value
  workerCount: 10                 # defaults to Hangfire built-in value
  invisibilityTimeoutInMinutes: 30
  maxJobArgumentsSizeToRenderInBytes: 4096  # max bytes of job args displayed in dashboard

Configuration class reference

[Configuration("hangfire")]
public class HangfireConfiguration
{
    public string Host { get; set; }
    public int Port { get; set; }
    public string Database { get; set; }
    public string User { get; set; }
    public string Password { get; set; }
    public bool InMemory { get; set; }
    public string AllowedDashboardHost { get; set; }
    public bool EnableJobsByFeatureManagementConfig { get; set; }
    public int? PollingIntervalInMs { get; set; }
    public int? WorkerCount { get; set; }
    public string[] Queues { get; set; } = [EnqueuedState.DefaultQueue];
    public int InvisibilityTimeoutInMinutes { get; set; } = 30;
    public int MaxJobArgumentsSizeToRenderInBytes { get; set; } = 4096;
    public string ConnectionString => $"Host={Host};Port={Port};Database={Database};Username={User};Password={Password};";
}

Usage

Defining jobs

Simple async job (no parameters)

Extend AsyncJob and implement ExecuteJobAsync:

public class SendDailyReportJob : AsyncJob
{
    private readonly IReportService _reportService;

    public SendDailyReportJob(ILogger<SendDailyReportJob> logger, IReportService reportService)
        : base(logger)
    {
        _reportService = reportService;
    }

    protected override async Task<object> ExecuteJobAsync(CancellationToken cancellationToken)
    {
        await _reportService.SendAsync(cancellationToken);
        return "done";
    }
}
Parameterized async job

Extend ParameterizedAsyncJob<T> when the job requires input:

public class ProcessOrderJob : ParameterizedAsyncJob<int>
{
    private readonly IOrderService _orderService;

    public ProcessOrderJob(ILogger<ProcessOrderJob> logger, IOrderService orderService)
        : base(logger)
    {
        _orderService = orderService;
    }

    protected override async Task<object> ExecuteJobAsync(int orderId, CancellationToken cancellationToken)
    {
        var result = await _orderService.ProcessAsync(orderId, cancellationToken);
        Logger.LogInformation("Processed order {OrderId}", orderId);
        return result;
    }
}

Note: Job input parameters and results are serialized to JSON and displayed in the Hangfire dashboard. Avoid passing sensitive data as job parameters.

Registering recurring jobs

Option 1 — attribute-based auto-registration

Annotate the job class with [RecurringJob]. The plugin discovers and registers it automatically at startup — no boilerplate required:

[RecurringJob("0 0 * * *")]              // every day at midnight UTC
public class SendDailyReportJob : AsyncJob { ... }

[RecurringJob("0 * * * *", "critical")]  // every hour, on the "critical" queue
public class CriticalHourlyJob : AsyncJob { ... }

The [RecurringJob] attribute accepts a standard cron expression and an optional queue name.

Option 2 — programmatic registration via ConfigureJobs

Register jobs in your plugin or host configuration using the IJobManager API:

public class MyPlugin : IPluginServiceConfiguration
{
    public void ConfigureServices(IServiceCollection serviceCollection)
    {
        serviceCollection.ConfigureJobs((jobs, _) =>
        {
            // simple recurring job
            jobs.RegisterRecurring<SendDailyReportJob>("0 0 * * *");

            // parameterized recurring job (passes the value at registration time)
            jobs.RegisterRecurring<ProcessOrderJob, int>(42, Cron.Daily());

            // recurring job on a specific queue with a name suffix (useful for multiple instances)
            jobs.RegisterRecurring<CriticalHourlyJob>("0 * * * *", queue: "critical", jobSuffix: "v2");
        });
    }
}

Fire-and-forget and delayed jobs via IJobManager

Inject IJobManager to enqueue or schedule jobs programmatically at runtime:

public class OrderController
{
    private readonly IJobManager _jobs;

    public OrderController(IJobManager jobs) => _jobs = jobs;

    public void PlaceOrder(int orderId)
    {
        // fire-and-forget
        _jobs.Enqueue<ProcessOrderJob, int>(orderId);

        // fire-and-forget on a specific queue
        _jobs.Enqueue<ProcessOrderJob, int>(orderId, queue: "critical");

        // simple job (no parameters)
        _jobs.Enqueue<SendDailyReportJob>();

        // delayed execution
        _jobs.Schedule<SendDailyReportJob>(TimeSpan.FromHours(1));
        _jobs.Schedule<ProcessOrderJob, int>(orderId, TimeSpan.FromMinutes(5));
    }
}

Querying job state via IJobManager

IJobManager exposes monitoring APIs to inspect the current state of the job store:

// all recurring jobs
IList<RecurringJobDto> recurring = jobManager.GetRecurringJobs();

// recurring jobs for a specific type, with optional predicate
IList<RecurringJobDto> myJobs = jobManager.GetRecurringJobs<ProcessOrderJob>();

// enqueued, processing, failed, fetched — all support type-filtered overloads
IList<EnqueuedJobDto>   enqueued   = jobManager.GetEnqueuedJobs<ProcessOrderJob>();
IList<ProcessingJobDto> processing = jobManager.GetProcessingJobs();
IList<FailedJobDto>     failed     = jobManager.GetFailedJobs();

// unified view across all states with optional predicate
IList<JobEntity> all = jobManager.GetJobs(e => e.Type == JobType.Failed);

// delete a recurring or background job
jobManager.DeleteRecurring("ProcessOrder");
jobManager.Delete(backgroundJobId);

Dashboard

The Hangfire dashboard is mounted at /hangfire. Access is restricted to the host configured in allowedDashboardHost. If the value is empty or not set, access is denied for all hosts.

Attributes

[RecurringJob(cronPattern, queue)]

Marks a class for automatic recurring job registration at startup. Accepts a cron pattern and an optional queue name.

[RecurringJob("*/5 * * * *", "default")]
public class PollExternalApiJob : AsyncJob { ... }

[JobTimeout(timeout, TimeUnit)]

Cancels the job after the specified duration. The cancellation token passed to ExecuteJobAsync is cancelled automatically.

[JobTimeout(30, TimeUnit.Seconds)]
public class QuickJob : AsyncJob { ... }

Supported TimeUnit values: Milliseconds, Seconds, Minutes, Hours, Days.

[JobTimeZone(TimeZoneInfo)]

Specifies the time zone used for cron schedule evaluation of recurring jobs (default: UTC).

[JobTimeZone(/* TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin") */)]
public class LocalTimeCronJob : AsyncJob { ... }

[UniquePerQueueAttribute(queue)]

Prevents duplicate job executions. If a job with the same type and arguments is already queued, the new one is deleted instead of enqueued.

[UniquePerQueueAttribute("default")]
public class ImportDataJob : AsyncJob { ... }

Optional properties:

  • CheckScheduledJobs — also check scheduled (delayed) jobs (default: false)
  • CheckRunningJobs — also check currently processing jobs (default: false)

[JobCleanupExpirationTimeAttribute(days)]

Controls how many days job results are retained in the storage backend before deletion.

[JobCleanupExpirationTimeAttribute(14)]
public class ArchiveJob : AsyncJob { ... }

Filters

LogJobExecutionFilter (always active)

Automatically logs a structured message at the start and finish of every job execution using the job's own logger type.

EnabledByFeatureFilter (opt-in)

Gates job execution on a feature flag using the Microsoft Feature Management system. Enable via configuration:

hangfire:
  enableJobsByFeatureManagementConfig: true

featureManagement:
  SendDailyReportJob: true   # job class name is the feature flag name
  ProcessOrderJob: false     # this job will be skipped

Works with both file-based feature management and the Dosaic Unleash plugin for dynamic runtime feature flags. Since the flag is resolved before each job execution, changes take effect at runtime with a delay based on how frequently the feature management source is refreshed.

Custom storage / server configuration (IHangfireConfigurator)

Implement IHangfireConfigurator (a IPluginConfigurator) to plug in a custom Hangfire storage backend or to configure the background server options:

public class MyHangfireConfigurator : IHangfireConfigurator
{
    // Set to true if your Configure() call registers a storage backend,
    // so the plugin skips its default PostgreSQL storage setup.
    public bool IncludesStorage => true;

    public void Configure(IGlobalConfiguration config)
    {
        config.UseRedisStorage("localhost:6379");
    }

    public void ConfigureServer(BackgroundJobServerOptions options)
    {
        options.WorkerCount = 5;
    }
}

All IHangfireConfigurator implementations are discovered automatically by the Dosaic plugin system.

Observability

Health check

A Hangfire readiness health check is registered automatically and is accessible via the standard Dosaic health endpoints (/health/readiness). It verifies that at least one Hangfire server is running.

OpenTelemetry tracing

Hangfire jobs are automatically instrumented with OpenTelemetry tracing via OpenTelemetry.Instrumentation.Hangfire.

In addition, every AsyncJob / ParameterizedAsyncJob<T> body runs inside a wrapper span emitted on the shared Dosaic ActivitySource (Tracing.SourceName):

  • Span nameGetType().FullName of the concrete job
  • Status — set to Ok on success, Error (with the exception attached) on failure
  • Log scopejob.type is also pushed onto the ILogger scope for the duration of the job

To enrich the span with business identifiers (e.g. tenant id, message id), override EnrichActivity:

public class ProcessOrderJob : ParameterizedAsyncJob<OrderId>
{
    protected override void EnrichActivity(Activity activity, OrderId value)
    {
        activity?.SetTag("order.id", value.ToString());
    }

    protected override Task<object> ExecuteJobAsync(OrderId value, CancellationToken ct) { ... }
}

AsyncJob has the same hook with the single-argument EnrichActivity(Activity activity) signature. Default implementation is a no-op.

PostgreSQL polling-noise filter

Hangfire's PostgreSQL job-storage polls the hangfire.* schema continuously, which produces a high volume of low-value SQL spans on whatever database instrumentation is enabled (Npgsql, EF Core, etc.). HangFirePlugin registers HangfireSqlNoiseProcessor as the first OpenTelemetry processor in the pipeline; it inspects db.statement / db.query.text / db.statement.text plus the activity display/operation name and clears ActivityTraceFlags.Recorded on any span referencing the hangfire schema, so the OTLP batch exporter drops them before send. No configuration knob — the filter is on whenever the Hangfire plugin is active.

Prometheus metrics

A background service (HangfireStatisticsMetricsReporter) collects Hangfire statistics every 60 seconds and publishes them as OpenTelemetry gauges:

Metric Description
hangfire_job_count_Succeeded Number of succeeded jobs
hangfire_job_count_Failed Number of failed jobs
hangfire_job_count_Scheduled Number of scheduled (delayed) jobs
hangfire_job_count_Processing Number of currently processing jobs
hangfire_job_count_Enqueued Number of enqueued jobs
hangfire_job_count_Deleted Number of deleted jobs
hangfire_job_count_Recurring Number of registered recurring jobs
hangfire_job_count_Servers Number of active Hangfire servers
hangfire_job_count_Queues Number of active queues
hangfire_job_count_RetryJobs Number of jobs currently awaiting retry

Job naming convention

The job ID used by Hangfire is derived from the class name by stripping the Job and Async suffixes. For example:

  • SendDailyReportJobSendDailyReport
  • ProcessOrderAsyncJobProcessOrder

When registering the same job type multiple times with jobSuffix, the suffix is appended: ProcessOrder_v2.

Further reading

Product Compatible and additional computed target framework versions.
.NET 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
1.2.31 44 5/28/2026
1.2.30 68 5/7/2026
1.2.29 59 5/5/2026
1.2.28 80 4/30/2026
1.2.27 66 4/29/2026
1.2.26 56 4/29/2026
1.2.25 75 4/27/2026
1.2.24 69 4/21/2026
1.2.23 78 4/14/2026
1.2.22 66 4/10/2026
1.2.21 62 4/10/2026
1.2.20 70 4/10/2026
1.2.19 63 4/9/2026
1.2.18 82 4/2/2026
1.2.17 65 4/1/2026
1.2.16 70 4/1/2026
1.2.15 71 3/31/2026
1.2.14 69 3/30/2026
1.2.13 65 3/26/2026
1.2.12 82 3/24/2026
Loading failed