diff --git a/.appveyor.yml b/.appveyor.yml
index ff471fb..c49aaa2 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -1,7 +1,7 @@
version: '{build}'
pull_requests:
do_not_increment_build_number: true
-image: Visual Studio 2019
+image: Visual Studio 2022
nuget:
disable_publish_on_pr: true
build_script:
diff --git a/.travis.yml b/.travis.yml
index eca6876..c59b90f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
language: csharp
mono: none
-dotnet: 5.0
+dotnet: 7.0
dist: xenial
env:
global:
diff --git a/src/Webenable.Hangfire.Contrib/AutoScheduleAttribute.cs b/src/Webenable.Hangfire.Contrib/AutoScheduleAttribute.cs
index fe25bea..f6c7a0e 100644
--- a/src/Webenable.Hangfire.Contrib/AutoScheduleAttribute.cs
+++ b/src/Webenable.Hangfire.Contrib/AutoScheduleAttribute.cs
@@ -1,34 +1,33 @@
using System;
-namespace Webenable.Hangfire.Contrib
+namespace Webenable.Hangfire.Contrib;
+
+///
+/// Specifies that the job should be automatically scheduled in Hangfire
+/// using the specified and .
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+public class AutoScheduleAttribute : Attribute
{
///
/// Specifies that the job should be automatically scheduled in Hangfire
- /// using the specified and .
+ /// using the specified and .
///
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
- public class AutoScheduleAttribute : Attribute
+ /// The name of the method to invoke the job with.
+ /// The CRON expression to schedule the job with.
+ public AutoScheduleAttribute(string methodName, string cronExpression)
{
- ///
- /// Specifies that the job should be automatically scheduled in Hangfire
- /// using the specified and .
- ///
- /// The name of the method to invoke the job with.
- /// The CRON expression to schedule the job with.
- public AutoScheduleAttribute(string methodName, string cronExpression)
- {
- MethodName = methodName;
- CronExpression = cronExpression;
- }
+ MethodName = methodName;
+ CronExpression = cronExpression;
+ }
- ///
- /// Gets the name of the method to invoke the job with.
- ///
- public string MethodName { get; }
+ ///
+ /// Gets the name of the method to invoke the job with.
+ ///
+ public string MethodName { get; }
- ///
- /// Gets the CRON expression to schedule the job with.
- ///
- public string CronExpression { get; }
- }
+ ///
+ /// Gets the CRON expression to schedule the job with.
+ ///
+ public string CronExpression { get; }
}
diff --git a/src/Webenable.Hangfire.Contrib/Crons.cs b/src/Webenable.Hangfire.Contrib/Crons.cs
index 9dcf1e7..ff17199 100644
--- a/src/Webenable.Hangfire.Contrib/Crons.cs
+++ b/src/Webenable.Hangfire.Contrib/Crons.cs
@@ -1,38 +1,37 @@
-namespace Webenable.Hangfire.Contrib
+namespace Webenable.Hangfire.Contrib;
+
+///
+/// Contains constant CRON expressions for convenience use in .
+///
+public static class Crons
{
///
- /// Contains constant CRON expressions for convenience use in .
+ /// Every minute.
///
- public static class Crons
- {
- ///
- /// Every minute.
- ///
- public const string Minutely = "* * * * *";
+ public const string Minutely = "* * * * *";
- ///
- /// Every hour.
- ///
- public const string Hourly = "0 * * * *";
+ ///
+ /// Every hour.
+ ///
+ public const string Hourly = "0 * * * *";
- ///
- /// Every day at 00:00.
- ///
- public const string Daily = "0 0 * * *";
+ ///
+ /// Every day at 00:00.
+ ///
+ public const string Daily = "0 0 * * *";
- ///
- /// Every Monday at 00:00.
- ///
- public const string Weekly = "0 0 * * 1";
+ ///
+ /// Every Monday at 00:00.
+ ///
+ public const string Weekly = "0 0 * * 1";
- ///
- /// Every first day of the month at 00:00.
- ///
- public const string Monthly = "0 0 1 * *";
+ ///
+ /// Every first day of the month at 00:00.
+ ///
+ public const string Monthly = "0 0 1 * *";
- ///
- /// Every year on January 1st at 00:00.
- ///
- public const string Yearly = "0 0 1 1 *";
- }
+ ///
+ /// Every year on January 1st at 00:00.
+ ///
+ public const string Yearly = "0 0 1 1 *";
}
diff --git a/src/Webenable.Hangfire.Contrib/HangfireContribOptions.cs b/src/Webenable.Hangfire.Contrib/HangfireContribOptions.cs
index e2c57e6..5a87ebc 100644
--- a/src/Webenable.Hangfire.Contrib/HangfireContribOptions.cs
+++ b/src/Webenable.Hangfire.Contrib/HangfireContribOptions.cs
@@ -2,55 +2,54 @@
using System.Reflection;
using Microsoft.AspNetCore.Http;
-namespace Webenable.Hangfire.Contrib
+namespace Webenable.Hangfire.Contrib;
+
+///
+/// Defines options for the Hangfire contrib extensions.
+///
+public class HangfireContribOptions
{
///
- /// Defines options for the Hangfire contrib extensions.
+ /// Gets or sets a value indicating whether the Hangfire background server should be enabled.
+ ///
+ public bool EnableServer { get; set; }
+
+ ///
+ /// Gets or sets the assemblies used to scan for job types.
+ /// By default the entry assembly of the application.
+ ///
+ public Assembly[] ScanningAssemblies { get; set; } = Array.Empty();
+
+ ///
+ /// Gets or sets options for the Hangfire dashboard.
+ ///
+ public DasbhoardOptions Dasbhoard { get; set; } = new DasbhoardOptions();
+
+ ///
+ /// Defines options for the Hangfire dashboard.
///
- public class HangfireContribOptions
+ public class DasbhoardOptions
{
///
- /// Gets or sets a value indicating whether the Hangfire background server should be enabled.
+ /// Gets or sets a value indicating whether the Hangfire dashboard should be enabled.
///
- public bool EnableServer { get; set; }
+ public bool Enabled { get; set; }
///
- /// Gets or sets the assemblies used to scan for job types.
- /// By default the entry assembly of the application.
+ /// Gets or sets a value indicating whether the Hangfire dashboard IP-based authorization filter should be enabled.
///
- public Assembly[] ScanningAssemblies { get; set; } = Array.Empty();
+ public bool EnableAuthorization { get; set; }
///
- /// Gets or sets options for the Hangfire dashboard.
+ /// Gets or sets a callback which gets invoked when authorizing a dashboard request.
+ /// If not specified, the default authorization polcy is IP-based using when IP-addresses are specified.
///
- public DasbhoardOptions Dasbhoard { get; set; } = new DasbhoardOptions();
+ public Func? AuthorizationCallback { get; set; }
///
- /// Defines options for the Hangfire dashboard.
+ /// Gets or sets the collection of IP-addresses which are allowed to access the Hangfire dashboard.
+ /// This is the default authorization policy.
///
- public class DasbhoardOptions
- {
- ///
- /// Gets or sets a value indicating whether the Hangfire dashboard should be enabled.
- ///
- public bool Enabled { get; set; }
-
- ///
- /// Gets or sets a value indicating whether the Hangfire dashboard IP-based authorization filter should be enabled.
- ///
- public bool EnableAuthorization { get; set; }
-
- ///
- /// Gets or sets a callback which gets invoked when authorizing a dashboard request.
- /// If not specified, the default authorization polcy is IP-based using when IP-addresses are specified.
- ///
- public Func? AuthorizationCallback { get; set; }
-
- ///
- /// Gets or sets the collection of IP-addresses which are allowed to access the Hangfire dashboard.
- /// This is the default authorization policy.
- ///
- public string[]? AllowedIps { get; set; }
- }
+ public string[]? AllowedIps { get; set; }
}
}
diff --git a/src/Webenable.Hangfire.Contrib/HangfireExtensions.cs b/src/Webenable.Hangfire.Contrib/HangfireExtensions.cs
index d9066f2..336956b 100644
--- a/src/Webenable.Hangfire.Contrib/HangfireExtensions.cs
+++ b/src/Webenable.Hangfire.Contrib/HangfireExtensions.cs
@@ -3,29 +3,27 @@
using Microsoft.Extensions.Logging;
using Webenable.Hangfire.Contrib.Internal;
-namespace Webenable.Hangfire.Contrib
+namespace Webenable.Hangfire.Contrib;
+
+///
+/// Extensions for logging in Hangfire jobs.
+///
+public static class HangfireExtensions
{
///
- /// Extensions for logging in Hangfire jobs.
+ /// Begins a logical operation scope for the given within the scope of a job.
///
- public static class HangfireExtensions
- {
- ///
- /// Begins a logical operation scope for the given within the scope of a job.
- ///
- /// The instance.
- /// The instance for the job.
- public static IDisposable BeginJobScope(this ILogger logger, PerformContext performContext) =>
- performContext != null ? logger.BeginScope(new PerformContextWrapper(performContext)) : NoopDisposable.Instance;
+ /// The instance.
+ /// The instance for the job.
+ public static IDisposable BeginJobScope(this ILogger logger, PerformContext performContext) =>
+ performContext != null ? logger.BeginScope(new PerformContextWrapper(performContext))! : NoopDisposable.Instance;
+ private class NoopDisposable : IDisposable
+ {
+ public static NoopDisposable Instance = new();
- private class NoopDisposable : IDisposable
+ public void Dispose()
{
- public static NoopDisposable Instance = new();
-
- public void Dispose()
- {
- }
}
}
}
diff --git a/src/Webenable.Hangfire.Contrib/HangfireJob.cs b/src/Webenable.Hangfire.Contrib/HangfireJob.cs
index 3afd99b..3d9675d 100644
--- a/src/Webenable.Hangfire.Contrib/HangfireJob.cs
+++ b/src/Webenable.Hangfire.Contrib/HangfireJob.cs
@@ -5,92 +5,91 @@
using Hangfire.Server;
using Microsoft.Extensions.Logging;
-namespace Webenable.Hangfire.Contrib
+namespace Webenable.Hangfire.Contrib;
+
+///
+/// Base class for Hangfire jobs which provides auto-scheduling and logging.
+///
+public abstract class HangfireJob
{
///
- /// Base class for Hangfire jobs which provides auto-scheduling and logging.
+ /// Initializes the base class with the specified .
///
- public abstract class HangfireJob
+ /// The logger factory to use for logging.
+ protected HangfireJob(ILoggerFactory loggerFactory)
{
- ///
- /// Initializes the base class with the specified .
- ///
- /// The logger factory to use for logging.
- protected HangfireJob(ILoggerFactory loggerFactory)
- {
- Logger = loggerFactory.CreateLogger(GetType());
- }
-
- ///
- /// Gets the instance for this job.
- ///
- protected ILogger Logger { get; }
+ Logger = loggerFactory.CreateLogger(GetType());
+ }
- ///
- /// Gets the perform context instance of this job.
- /// May be null.
- ///
- protected PerformContext? PerformContext { get; private set; }
+ ///
+ /// Gets the instance for this job.
+ ///
+ protected ILogger Logger { get; }
- ///
- /// Executes the job.
- ///
- ///
- /// The context in which the job is performed. Populated by Hangfire.
- /// It is safe to pass null in unit tests or other manual scenarios.
- ///
- ///
- /// The . Populated by Hangfire.
- /// It is safe to pass null in unit tests or other manual scenarios,
- /// a new instance of will be created in that case.
- ///
- public async Task ExecuteAsync(PerformContext performContext, IJobCancellationToken cancellationToken)
- {
- PerformContext = performContext;
- var jobId = performContext?.BackgroundJob?.Id;
+ ///
+ /// Gets the perform context instance of this job.
+ /// May be null.
+ ///
+ protected PerformContext? PerformContext { get; private set; }
- cancellationToken?.ThrowIfCancellationRequested();
+ ///
+ /// Executes the job.
+ ///
+ ///
+ /// The context in which the job is performed. Populated by Hangfire.
+ /// It is safe to pass null in unit tests or other manual scenarios.
+ ///
+ ///
+ /// The . Populated by Hangfire.
+ /// It is safe to pass null in unit tests or other manual scenarios,
+ /// a new instance of will be created in that case.
+ ///
+ public async Task ExecuteAsync(PerformContext performContext, IJobCancellationToken cancellationToken)
+ {
+ PerformContext = performContext;
+ var jobId = performContext?.BackgroundJob?.Id;
- IDisposable? jobScope = null;
- IDisposable? performContextScope = null;
+ cancellationToken?.ThrowIfCancellationRequested();
- // Perform context is optional, e.g. may be null in unit tests
- if (performContext != null)
- {
- if (!string.IsNullOrEmpty(jobId))
- {
- jobScope = Logger.BeginScope("Job {JobId}", jobId);
- }
+ IDisposable? jobScope = null;
+ IDisposable? performContextScope = null;
- performContextScope = Logger.BeginJobScope(performContext);
- }
-
- Logger.LogDebug("Starting job {JobId}", jobId);
- try
- {
- await ExecuteCoreAsync(cancellationToken ?? new JobCancellationToken(false));
- Logger.LogDebug("Finished job {JobId}", jobId);
- }
- catch (Exception ex)
+ // Perform context is optional, e.g. may be null in unit tests
+ if (performContext != null)
+ {
+ if (!string.IsNullOrEmpty(jobId))
{
- Logger.LogError(ex, "Failed executing job {JobId}/{JobName}: {JobException}", jobId, GetType().Name, ex.ToStringDemystified());
- throw;
+ jobScope = Logger.BeginScope("Job {JobId}", jobId);
}
- performContextScope?.Dispose();
- jobScope?.Dispose();
+ performContextScope = Logger.BeginJobScope(performContext);
}
- ///
- /// Executes the inner logic of the job.
- ///
- /// The .
- protected abstract Task ExecuteCoreAsync(IJobCancellationToken cancellationToken);
+ Logger.LogDebug("Starting job {JobId}", jobId);
+ try
+ {
+ await ExecuteCoreAsync(cancellationToken ?? new JobCancellationToken(false));
+ Logger.LogDebug("Finished job {JobId}", jobId);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Failed executing job {JobId}/{JobName}: {JobException}", jobId, GetType().Name, ex.ToStringDemystified());
+ throw;
+ }
- ///
- /// Gets the schedule for automatically scheduled jobs.
- /// Default is null which means that the job is not automatically scheduled.
- ///
- public virtual string? Schedule => null;
+ performContextScope?.Dispose();
+ jobScope?.Dispose();
}
+
+ ///
+ /// Executes the inner logic of the job.
+ ///
+ /// The .
+ protected abstract Task ExecuteCoreAsync(IJobCancellationToken cancellationToken);
+
+ ///
+ /// Gets the schedule for automatically scheduled jobs.
+ /// Default is null which means that the job is not automatically scheduled.
+ ///
+ public virtual string? Schedule => null;
}
diff --git a/src/Webenable.Hangfire.Contrib/HangfireServiceCollectionExtensions.cs b/src/Webenable.Hangfire.Contrib/HangfireServiceCollectionExtensions.cs
index 7a86cd3..716249a 100644
--- a/src/Webenable.Hangfire.Contrib/HangfireServiceCollectionExtensions.cs
+++ b/src/Webenable.Hangfire.Contrib/HangfireServiceCollectionExtensions.cs
@@ -7,45 +7,44 @@
using Microsoft.Extensions.Logging;
using Webenable.Hangfire.Contrib.Internal;
-namespace Webenable.Hangfire.Contrib
+namespace Webenable.Hangfire.Contrib;
+
+///
+/// Extensions for Hangfire contrib.
+///
+public static class HangfireServiceCollectionExtensions
{
///
- /// Extensions for Hangfire contrib.
+ /// Adds Hangfire contrib extensions.
///
- public static class HangfireServiceCollectionExtensions
- {
- ///
- /// Adds Hangfire contrib extensions.
- ///
- public static IServiceCollection AddHangfireContrib(this IServiceCollection services) => AddHangfireContrib(services, configAction: null);
+ public static IServiceCollection AddHangfireContrib(this IServiceCollection services) => AddHangfireContrib(services, configAction: null);
- ///
- /// Adds Hangfire contrib extensions and configures Hangfire with the specified action.
- ///
- public static IServiceCollection AddHangfireContrib(this IServiceCollection services, Action? configAction)
+ ///
+ /// Adds Hangfire contrib extensions and configures Hangfire with the specified action.
+ ///
+ public static IServiceCollection AddHangfireContrib(this IServiceCollection services, Action? configAction)
+ {
+ services.AddHangfire(c =>
{
- services.AddHangfire(c =>
- {
- c.UseConsole();
- configAction?.Invoke(c);
- });
+ c.UseConsole();
+ configAction?.Invoke(c);
+ });
- // Add Hangfire options instances to the ASP.NET options infrastructure
- services.Configure(o => { });
- services.Configure(o => { });
+ // Add Hangfire options instances to the ASP.NET options infrastructure
+ services.Configure(o => { });
+ services.Configure(o => { });
- // Configure default options for Hangfire.Contrib
- services.Configure(o =>
- {
- o.EnableServer = true;
- o.Dasbhoard.Enabled = true;
- o.ScanningAssemblies = new[] { Assembly.GetEntryAssembly()! };
- });
+ // Configure default options for Hangfire.Contrib
+ services.Configure(o =>
+ {
+ o.EnableServer = true;
+ o.Dasbhoard.Enabled = true;
+ o.ScanningAssemblies = new[] { Assembly.GetEntryAssembly()! };
+ });
- services.AddSingleton();
- services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
- return services;
- }
+ return services;
}
}
diff --git a/src/Webenable.Hangfire.Contrib/Internal/DashboardAuthorizationFilter.cs b/src/Webenable.Hangfire.Contrib/Internal/DashboardAuthorizationFilter.cs
index a8ab9ed..1e8a3df 100644
--- a/src/Webenable.Hangfire.Contrib/Internal/DashboardAuthorizationFilter.cs
+++ b/src/Webenable.Hangfire.Contrib/Internal/DashboardAuthorizationFilter.cs
@@ -6,90 +6,89 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-namespace Webenable.Hangfire.Contrib.Internal
+namespace Webenable.Hangfire.Contrib.Internal;
+
+public class DashboardAuthorizationFilter : IDashboardAuthorizationFilter
{
- public class DashboardAuthorizationFilter : IDashboardAuthorizationFilter
+ private readonly IWebHostEnvironment? _environment;
+ private readonly string[]? _allowedIps;
+ private readonly ILogger _logger;
+ private readonly Func? _authorizationCallback;
+
+ public DashboardAuthorizationFilter(Func authorizationCallback, ILoggerFactory loggerFactory)
{
- private readonly IWebHostEnvironment? _environment;
- private readonly string[]? _allowedIps;
- private readonly ILogger _logger;
- private readonly Func? _authorizationCallback;
+ _authorizationCallback = authorizationCallback;
+ _logger = loggerFactory.CreateLogger();
+ }
- public DashboardAuthorizationFilter(Func authorizationCallback, ILoggerFactory loggerFactory)
- {
- _authorizationCallback = authorizationCallback;
- _logger = loggerFactory.CreateLogger();
- }
+ public DashboardAuthorizationFilter(IWebHostEnvironment environment, string[] allowedIps, ILoggerFactory loggerFactory)
+ {
+ _environment = environment;
+ _allowedIps = allowedIps;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ public bool Authorize(DashboardContext context)
+ {
+ var httpContext = context.GetHttpContext();
- public DashboardAuthorizationFilter(IWebHostEnvironment environment, string[] allowedIps, ILoggerFactory loggerFactory)
+ // Invoke the custom specified authorization callback if specified.
+ // Otherwise execute IP-based authorization.
+ if (_authorizationCallback != null)
{
- _environment = environment;
- _allowedIps = allowedIps;
- _logger = loggerFactory.CreateLogger();
+ return InvokeAuthorizationCallback(httpContext);
}
- public bool Authorize(DashboardContext context)
+ return AuthorizeIpAddress(httpContext);
+ }
+
+ private bool InvokeAuthorizationCallback(HttpContext httpContext)
+ {
+ if (_authorizationCallback?.Invoke(httpContext) == true)
{
- var httpContext = context.GetHttpContext();
+ _logger.LogDebug("Grant access to Hangfire dashboard");
+ return true;
+ }
- // Invoke the custom specified authorization callback if specified.
- // Otherwise execute IP-based authorization.
- if (_authorizationCallback != null)
- {
- return InvokeAuthorizationCallback(httpContext);
- }
+ _logger.LogWarning("Deny access to Hangfire dashboard");
+ return false;
+ }
- return AuthorizeIpAddress(httpContext);
+ private bool AuthorizeIpAddress(HttpContext httpContext)
+ {
+ if (_environment?.IsDevelopment() == true)
+ {
+ // Always allow requests in development environment.
+ _logger.LogDebug("Grant access to Hangfire dashboard in development environment");
+ return true;
}
- private bool InvokeAuthorizationCallback(HttpContext httpContext)
+ if (_allowedIps == null || _allowedIps.Length == 0)
{
- if (_authorizationCallback?.Invoke(httpContext) == true)
- {
- _logger.LogDebug("Grant access to Hangfire dashboard");
- return true;
- }
-
- _logger.LogWarning("Deny access to Hangfire dashboard");
+ _logger.LogWarning("Deny access to dashboard: no allowed IP addresses configured");
return false;
}
- private bool AuthorizeIpAddress(HttpContext httpContext)
+ // Resolve remote IP addresses from the forwarded headers as well as the default header.
+ var ips = httpContext
+ .Request
+ .Headers["X-Forwarded-For"]
+ .ToString()
+ .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ .Append(httpContext.Connection.RemoteIpAddress?.ToString())
+ .Select(r => r?.Trim())
+ .Where(x => !string.IsNullOrEmpty(x));
+
+ foreach (var ip in ips)
{
- if (_environment?.IsDevelopment() == true)
+ if (_allowedIps.Contains(ip))
{
- // Always allow requests in development environment.
- _logger.LogDebug("Grant access to Hangfire dashboard in development environment");
+ _logger.LogDebug("Grant access to Hangfire dashboard for IP-address {IpAddress}", ip);
return true;
}
-
- if (_allowedIps == null || _allowedIps.Length == 0)
- {
- _logger.LogWarning("Deny access to dashboard: no allowed IP addresses configured");
- return false;
- }
-
- // Resolve remote IP addresses from the forwarded headers as well as the default header.
- var ips = httpContext
- .Request
- .Headers["X-Forwarded-For"]
- .ToString()
- .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
- .Append(httpContext.Connection.RemoteIpAddress?.ToString())
- .Select(r => r?.Trim())
- .Where(x => !string.IsNullOrEmpty(x));
-
- foreach (var ip in ips)
- {
- if (_allowedIps.Contains(ip))
- {
- _logger.LogDebug("Grant access to Hangfire dashboard for IP-address {IpAddress}", ip);
- return true;
- }
- }
-
- _logger.LogWarning("Deny access to Hangfire dashboard for IP-addresses {IpAddresses}", string.Join(", ", ips));
- return false;
}
+
+ _logger.LogWarning("Deny access to Hangfire dashboard for IP-addresses {IpAddresses}", string.Join(", ", ips));
+ return false;
}
}
diff --git a/src/Webenable.Hangfire.Contrib/Internal/HangfireContribStartupFilter.cs b/src/Webenable.Hangfire.Contrib/Internal/HangfireContribStartupFilter.cs
index 913062e..c77d734 100644
--- a/src/Webenable.Hangfire.Contrib/Internal/HangfireContribStartupFilter.cs
+++ b/src/Webenable.Hangfire.Contrib/Internal/HangfireContribStartupFilter.cs
@@ -8,127 +8,126 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-namespace Webenable.Hangfire.Contrib.Internal
+namespace Webenable.Hangfire.Contrib.Internal;
+
+public class HangfireContribStartupFilter : IStartupFilter
{
- public class HangfireContribStartupFilter : IStartupFilter
+ private readonly HangfireContribOptions _contribOptions;
+ private readonly BackgroundJobServerOptions _backgroundJobServerOptions;
+ private readonly DashboardOptions _dashboardOptions;
+ private readonly IRecurringJobManager _recurringJobManager;
+ private readonly ILogger _logger;
+
+ public HangfireContribStartupFilter(
+ IOptions options,
+ IOptions backgroundJobServerOptions,
+ IOptions dashboardOptions,
+ IRecurringJobManager recurringJobManager,
+ ILogger logger)
{
- private readonly HangfireContribOptions _contribOptions;
- private readonly BackgroundJobServerOptions _backgroundJobServerOptions;
- private readonly DashboardOptions _dashboardOptions;
- private readonly IRecurringJobManager _recurringJobManager;
- private readonly ILogger _logger;
+ _contribOptions = options.Value;
+ _backgroundJobServerOptions = backgroundJobServerOptions.Value;
+ _dashboardOptions = dashboardOptions.Value;
+ _recurringJobManager = recurringJobManager;
+ _logger = logger;
+ }
- public HangfireContribStartupFilter(
- IOptions options,
- IOptions backgroundJobServerOptions,
- IOptions dashboardOptions,
- IRecurringJobManager recurringJobManager,
- ILogger logger)
+ public Action Configure(Action next) =>
+ app =>
{
- _contribOptions = options.Value;
- _backgroundJobServerOptions = backgroundJobServerOptions.Value;
- _dashboardOptions = dashboardOptions.Value;
- _recurringJobManager = recurringJobManager;
- _logger = logger;
- }
-
- public Action Configure(Action next) =>
- app =>
+ if (_contribOptions.EnableServer)
{
- if (_contribOptions.EnableServer)
- {
- _logger.LogInformation("Enabling Hangfire server");
- app.UseHangfireServer(_backgroundJobServerOptions);
- }
+ _logger.LogInformation("Enabling Hangfire server");
+ app.UseHangfireServer(_backgroundJobServerOptions);
+ }
- if (_contribOptions.Dasbhoard.Enabled)
- {
- ConfigureDashboard(app);
- }
+ if (_contribOptions.Dasbhoard.Enabled)
+ {
+ ConfigureDashboard(app);
+ }
- RegisterJobs(app);
+ RegisterJobs(app);
- next(app);
- };
+ next(app);
+ };
- private void ConfigureDashboard(IApplicationBuilder app)
+ private void ConfigureDashboard(IApplicationBuilder app)
+ {
+ _logger.LogInformation("Enabling Hangfire dashboard");
+ var dashboardOptions = _dashboardOptions ?? new DashboardOptions();
+ if (_contribOptions.Dasbhoard.EnableAuthorization)
{
- _logger.LogInformation("Enabling Hangfire dashboard");
- var dashboardOptions = _dashboardOptions ?? new DashboardOptions();
- if (_contribOptions.Dasbhoard.EnableAuthorization)
- {
- var loggerFactory = app.ApplicationServices.GetRequiredService();
+ var loggerFactory = app.ApplicationServices.GetRequiredService();
- DashboardAuthorizationFilter dashboardAuthorizationFilter;
- if (_contribOptions.Dasbhoard.AuthorizationCallback != null)
- {
- _logger.LogInformation("Configuring Hangfire dashboard authorization with custom callback");
- dashboardAuthorizationFilter = new DashboardAuthorizationFilter(_contribOptions.Dasbhoard.AuthorizationCallback, loggerFactory);
- }
- else if (_contribOptions.Dasbhoard.AllowedIps?.Length > 0)
- {
- _logger.LogInformation("Configuring Hangfire IP-based dashboard authorization");
- dashboardAuthorizationFilter = new DashboardAuthorizationFilter(app.ApplicationServices.GetRequiredService(), _contribOptions.Dasbhoard.AllowedIps, loggerFactory);
- }
- else
- {
- throw new InvalidOperationException("No custom authorization callback or allowed IP-addresses configured for Hangfire dashboard authorization.");
- }
-
- dashboardOptions.Authorization = new[] { dashboardAuthorizationFilter };
+ DashboardAuthorizationFilter dashboardAuthorizationFilter;
+ if (_contribOptions.Dasbhoard.AuthorizationCallback != null)
+ {
+ _logger.LogInformation("Configuring Hangfire dashboard authorization with custom callback");
+ dashboardAuthorizationFilter = new DashboardAuthorizationFilter(_contribOptions.Dasbhoard.AuthorizationCallback, loggerFactory);
+ }
+ else if (_contribOptions.Dasbhoard.AllowedIps?.Length > 0)
+ {
+ _logger.LogInformation("Configuring Hangfire IP-based dashboard authorization");
+ dashboardAuthorizationFilter = new DashboardAuthorizationFilter(app.ApplicationServices.GetRequiredService(), _contribOptions.Dasbhoard.AllowedIps, loggerFactory);
+ }
+ else
+ {
+ throw new InvalidOperationException("No custom authorization callback or allowed IP-addresses configured for Hangfire dashboard authorization.");
}
- app.UseHangfireDashboard(options: dashboardOptions);
+ dashboardOptions.Authorization = new[] { dashboardAuthorizationFilter };
}
- private static readonly MethodInfo HangfireJobExecuteAsyncMethod = typeof(HangfireJob).GetMethod(nameof(HangfireJob.ExecuteAsync))!;
+ app.UseHangfireDashboard(options: dashboardOptions);
+ }
+
+ private static readonly MethodInfo HangfireJobExecuteAsyncMethod = typeof(HangfireJob).GetMethod(nameof(HangfireJob.ExecuteAsync))!;
- private void RegisterJobs(IApplicationBuilder app)
+ private void RegisterJobs(IApplicationBuilder app)
+ {
+ using var scope = app.ApplicationServices.CreateScope();
+ var sp = scope.ServiceProvider;
+ var hangfireJobType = typeof(HangfireJob);
+ foreach (var assembly in _contribOptions.ScanningAssemblies)
{
- using var scope = app.ApplicationServices.CreateScope();
- var sp = scope.ServiceProvider;
- var hangfireJobType = typeof(HangfireJob);
- foreach (var assembly in _contribOptions.ScanningAssemblies)
+ foreach (var candidate in assembly.ExportedTypes)
{
- foreach (var candidate in assembly.ExportedTypes)
+ if (candidate.IsAbstract)
{
- if (candidate.IsAbstract)
- {
- // Skip abstract types
- continue;
- }
+ // Skip abstract types
+ continue;
+ }
- if (hangfireJobType.IsAssignableFrom(candidate) && candidate != hangfireJobType)
+ if (hangfireJobType.IsAssignableFrom(candidate) && candidate != hangfireJobType)
+ {
+ try
{
- try
+ var jobInstance = (HangfireJob)ActivatorUtilities.CreateInstance(sp, candidate);
+ if (!string.IsNullOrEmpty(jobInstance.Schedule))
{
- var jobInstance = (HangfireJob)ActivatorUtilities.CreateInstance(sp, candidate);
- if (!string.IsNullOrEmpty(jobInstance.Schedule))
- {
- _logger.LogInformation("Auto-scheduling job {JobName} with schedule {JobSchedule}", candidate.Name, jobInstance.Schedule);
- _recurringJobManager.AddOrUpdate(
- candidate.Name,
- new Job(candidate, HangfireJobExecuteAsyncMethod, null, null),
- jobInstance.Schedule);
- }
- else
- {
- _logger.LogDebug("Job {JobName} auto-scheduling is disabled", candidate.Name);
- }
+ _logger.LogInformation("Auto-scheduling job {JobName} with schedule {JobSchedule}", candidate.Name, jobInstance.Schedule);
+ _recurringJobManager.AddOrUpdate(
+ candidate.Name,
+ new Job(candidate, HangfireJobExecuteAsyncMethod, null, null),
+ jobInstance.Schedule);
}
- catch (Exception ex)
+ else
{
- _logger.LogError(ex, "Unable to activate job {JobName}: {ExceptionMessage}", candidate.Name, ex.Message);
+ _logger.LogDebug("Job {JobName} auto-scheduling is disabled", candidate.Name);
}
}
- else
+ catch (Exception ex)
{
- var scheduleAttr = candidate.GetCustomAttribute();
- if (scheduleAttr != null)
- {
- _logger.LogInformation("Auto-scheduling job {JobName} via [AutoScheduled] attribute with schedule {JobSchedule}", candidate.Name, scheduleAttr.CronExpression);
- _recurringJobManager.AddOrUpdate(candidate.Name, new Job(candidate, candidate.GetMethod(scheduleAttr.MethodName)), scheduleAttr.CronExpression);
- }
+ _logger.LogError(ex, "Unable to activate job {JobName}: {ExceptionMessage}", candidate.Name, ex.Message);
+ }
+ }
+ else
+ {
+ var scheduleAttr = candidate.GetCustomAttribute();
+ if (scheduleAttr != null)
+ {
+ _logger.LogInformation("Auto-scheduling job {JobName} via [AutoScheduled] attribute with schedule {JobSchedule}", candidate.Name, scheduleAttr.CronExpression);
+ _recurringJobManager.AddOrUpdate(candidate.Name, new Job(candidate, candidate.GetMethod(scheduleAttr.MethodName)), scheduleAttr.CronExpression);
}
}
}
diff --git a/src/Webenable.Hangfire.Contrib/Internal/HangfireLogger.cs b/src/Webenable.Hangfire.Contrib/Internal/HangfireLogger.cs
index 15d6241..c40c216 100644
--- a/src/Webenable.Hangfire.Contrib/Internal/HangfireLogger.cs
+++ b/src/Webenable.Hangfire.Contrib/Internal/HangfireLogger.cs
@@ -5,81 +5,80 @@
using Hangfire.Server;
using Microsoft.Extensions.Logging;
-namespace Webenable.Hangfire.Contrib.Internal
+namespace Webenable.Hangfire.Contrib.Internal;
+
+public class HangfireLogger : ILogger
{
- public class HangfireLogger : ILogger
- {
- public IExternalScopeProvider? ScopeProvider { get; internal set; }
+ public IExternalScopeProvider? ScopeProvider { get; internal set; }
- public IDisposable BeginScope(TState state) => ScopeProvider?.Push(state) ?? NoopDisposable.Instance;
+ public IDisposable BeginScope(TState state) => ScopeProvider?.Push(state) ?? NoopDisposable.Instance;
- public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None && ScopeProvider != null;
+ public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None && ScopeProvider != null;
- public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
+ {
+ if (!IsEnabled(logLevel))
{
- if (!IsEnabled(logLevel))
- {
- return;
- }
+ return;
+ }
+
+ try
+ {
+ PerformContext? ctx = null;
+ var msgBuilder = new StringBuilder();
- try
+ var scopeProvider = ScopeProvider;
+ scopeProvider?.ForEachScope((scopeValue, scopeState) =>
{
- PerformContext? ctx = null;
- var msgBuilder = new StringBuilder();
+ string? msg = null;
+ if (scopeValue is IReadOnlyList> kvp)
+ {
+ msg = kvp.ToString();
+ }
+ if (scopeValue is string str)
+ {
+ msg = str;
+ }
+ if (msg != null && !msg.StartsWith("Job"))
+ {
+ msgBuilder.Append(msgBuilder.Length == 0 ? "" : " => ").Append(msg);
+ return;
+ }
- var scopeProvider = ScopeProvider;
- scopeProvider?.ForEachScope((scopeValue, scopeState) =>
+ if (scopeValue is PerformContext performContext)
{
- string? msg = null;
- if (scopeValue is IReadOnlyList> kvp)
- {
- msg = kvp.ToString();
- }
- if (scopeValue is string str)
- {
- msg = str;
- }
- if (msg != null && !msg.StartsWith("Job"))
- {
- msgBuilder.Append(msgBuilder.Length == 0 ? "" : " => ").Append(msg);
- return;
- }
+ ctx = performContext;
+ }
+ }, state);
- if (scopeValue is PerformContext performContext)
- {
- ctx = performContext;
- }
- }, state);
+ if (ctx != null)
+ {
+ msgBuilder.Append(msgBuilder.Length == 0 ? "" : " => ").Append(state?.ToString());
- if (ctx != null)
+ var color = logLevel switch
{
- msgBuilder.Append(msgBuilder.Length == 0 ? "" : " => ").Append(state?.ToString());
+ LogLevel.Critical or LogLevel.Error => ConsoleTextColor.Red,
+ LogLevel.Warning => ConsoleTextColor.Yellow,
+ _ => ConsoleTextColor.White,
+ };
- var color = logLevel switch
- {
- LogLevel.Critical or LogLevel.Error => ConsoleTextColor.Red,
- LogLevel.Warning => ConsoleTextColor.Yellow,
- _ => ConsoleTextColor.White,
- };
-
- ctx.WriteLine(color, msgBuilder.ToString());
- }
- }
- catch (Exception ex)
- {
- // Logging should never throw an exception
- // Write the exceptions to the console for visiblity
- Console.WriteLine($"Failed to write Hangfire log: {ex}");
+ ctx.WriteLine(color, msgBuilder.ToString());
}
}
-
- private class NoopDisposable : IDisposable
+ catch (Exception ex)
{
- public static NoopDisposable Instance = new();
+ // Logging should never throw an exception
+ // Write the exceptions to the console for visiblity
+ Console.WriteLine($"Failed to write Hangfire log: {ex}");
+ }
+ }
- public void Dispose()
- {
- }
+ private class NoopDisposable : IDisposable
+ {
+ public static NoopDisposable Instance = new();
+
+ public void Dispose()
+ {
}
}
}
diff --git a/src/Webenable.Hangfire.Contrib/Internal/HangfireLoggerProvider.cs b/src/Webenable.Hangfire.Contrib/Internal/HangfireLoggerProvider.cs
index 7299f79..3ec7907 100644
--- a/src/Webenable.Hangfire.Contrib/Internal/HangfireLoggerProvider.cs
+++ b/src/Webenable.Hangfire.Contrib/Internal/HangfireLoggerProvider.cs
@@ -1,21 +1,20 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
-namespace Webenable.Hangfire.Contrib.Internal
-{
- public sealed class HangfireLoggerProvider : ILoggerProvider, ISupportExternalScope
- {
- private readonly ConcurrentDictionary _loggers = new();
+namespace Webenable.Hangfire.Contrib.Internal;
- private IExternalScopeProvider? _scopeProvider;
+public sealed class HangfireLoggerProvider : ILoggerProvider, ISupportExternalScope
+{
+ private readonly ConcurrentDictionary _loggers = new();
- public ILogger CreateLogger(string categoryName) =>
- _loggers.GetOrAdd(categoryName, new HangfireLogger { ScopeProvider = _scopeProvider });
+ private IExternalScopeProvider? _scopeProvider;
- public void Dispose()
- {
- }
+ public ILogger CreateLogger(string categoryName) =>
+ _loggers.GetOrAdd(categoryName, new HangfireLogger { ScopeProvider = _scopeProvider });
- public void SetScopeProvider(IExternalScopeProvider scopeProvider) => _scopeProvider = scopeProvider;
+ public void Dispose()
+ {
}
+
+ public void SetScopeProvider(IExternalScopeProvider scopeProvider) => _scopeProvider = scopeProvider;
}
diff --git a/src/Webenable.Hangfire.Contrib/Internal/PerformContextWrapper.cs b/src/Webenable.Hangfire.Contrib/Internal/PerformContextWrapper.cs
index f98f891..14d0dad 100644
--- a/src/Webenable.Hangfire.Contrib/Internal/PerformContextWrapper.cs
+++ b/src/Webenable.Hangfire.Contrib/Internal/PerformContextWrapper.cs
@@ -1,13 +1,12 @@
using Hangfire.Server;
-namespace Webenable.Hangfire.Contrib.Internal
+namespace Webenable.Hangfire.Contrib.Internal;
+
+internal class PerformContextWrapper : PerformContext
{
- internal class PerformContextWrapper : PerformContext
+ public PerformContextWrapper(PerformContext context) : base(context)
{
- public PerformContextWrapper(PerformContext context) : base(context)
- {
- }
-
- public override string ToString() => BackgroundJob.Job.Type.Name;
}
+
+ public override string ToString() => BackgroundJob.Job.Type.Name;
}
diff --git a/src/Webenable.Hangfire.Contrib/JobExpirationAttribute.cs b/src/Webenable.Hangfire.Contrib/JobExpirationAttribute.cs
index cb38b5e..1c07c5b 100644
--- a/src/Webenable.Hangfire.Contrib/JobExpirationAttribute.cs
+++ b/src/Webenable.Hangfire.Contrib/JobExpirationAttribute.cs
@@ -3,31 +3,30 @@
using Hangfire.States;
using Hangfire.Storage;
-namespace Webenable.Hangfire.Contrib
+namespace Webenable.Hangfire.Contrib;
+
+///
+/// Sets the expiration timeout of a job.
+///
+public class JobExpirationAttribute : JobFilterAttribute, IApplyStateFilter
{
///
/// Sets the expiration timeout of a job.
///
- public class JobExpirationAttribute : JobFilterAttribute, IApplyStateFilter
+ public JobExpirationAttribute()
{
- ///
- /// Sets the expiration timeout of a job.
- ///
- public JobExpirationAttribute()
- {
- }
+ }
- ///
- /// Gets or sets the expiration timeout duration in days.
- ///
- public int Days { get; set; }
+ ///
+ /// Gets or sets the expiration timeout duration in days.
+ ///
+ public int Days { get; set; }
- ///
- public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction) =>
- context.JobExpirationTimeout = TimeSpan.FromDays(Days);
+ ///
+ public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction) =>
+ context.JobExpirationTimeout = TimeSpan.FromDays(Days);
- ///
- public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction) =>
- context.JobExpirationTimeout = TimeSpan.FromDays(Days);
- }
+ ///
+ public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction) =>
+ context.JobExpirationTimeout = TimeSpan.FromDays(Days);
}
diff --git a/src/Webenable.Hangfire.Contrib/Webenable.Hangfire.Contrib.csproj b/src/Webenable.Hangfire.Contrib/Webenable.Hangfire.Contrib.csproj
index 2fad9f1..ee7b04b 100644
--- a/src/Webenable.Hangfire.Contrib/Webenable.Hangfire.Contrib.csproj
+++ b/src/Webenable.Hangfire.Contrib/Webenable.Hangfire.Contrib.csproj
@@ -1,7 +1,7 @@
- netcoreapp3.1;net5.0
- 3.1.1
+ net6.0;net7.0
+ 4.0.0
latest
Useful and opinionated set of ASP.NET Core integration extensions for Hangfire.
true
@@ -18,8 +18,8 @@
-
-
+
+
diff --git a/test/Webenable.Hangfire.Contrib.Tests/HangfireJobTests.cs b/test/Webenable.Hangfire.Contrib.Tests/HangfireJobTests.cs
index 5a4e220..0139fdb 100644
--- a/test/Webenable.Hangfire.Contrib.Tests/HangfireJobTests.cs
+++ b/test/Webenable.Hangfire.Contrib.Tests/HangfireJobTests.cs
@@ -3,36 +3,35 @@
using Microsoft.Extensions.Logging;
using Xunit;
-namespace Webenable.Hangfire.Contrib.Tests
+namespace Webenable.Hangfire.Contrib.Tests;
+
+public class HangfireJobTests
{
- public class HangfireJobTests
+ [Fact]
+ public async Task ExecutesWithNullParams()
{
- [Fact]
- public async Task ExecutesWithNullParams()
- {
- // Arrange
- var job = new FooJob(new LoggerFactory());
+ // Arrange
+ var job = new FooJob(new LoggerFactory());
- // Act
- await job.ExecuteAsync(null, null);
+ // Act
+ await job.ExecuteAsync(null, null);
- // Assert
- Assert.True(job.Executed);
- }
+ // Assert
+ Assert.True(job.Executed);
}
+}
- public class FooJob : HangfireJob
+public class FooJob : HangfireJob
+{
+ public FooJob(ILoggerFactory loggerFactory) : base(loggerFactory)
{
- public FooJob(ILoggerFactory loggerFactory) : base(loggerFactory)
- {
- }
-
- protected override Task ExecuteCoreAsync(IJobCancellationToken cancellationToken)
- {
- Executed = true;
- return Task.CompletedTask;
- }
+ }
- internal bool Executed { get; set; }
+ protected override Task ExecuteCoreAsync(IJobCancellationToken cancellationToken)
+ {
+ Executed = true;
+ return Task.CompletedTask;
}
+
+ internal bool Executed { get; set; }
}
diff --git a/test/Webenable.Hangfire.Contrib.Tests/Webenable.Hangfire.Contrib.Tests.csproj b/test/Webenable.Hangfire.Contrib.Tests/Webenable.Hangfire.Contrib.Tests.csproj
index dd1262b..de9cf0c 100644
--- a/test/Webenable.Hangfire.Contrib.Tests/Webenable.Hangfire.Contrib.Tests.csproj
+++ b/test/Webenable.Hangfire.Contrib.Tests/Webenable.Hangfire.Contrib.Tests.csproj
@@ -1,11 +1,14 @@
- net5.0
+ net7.0
-
-
-
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+