I have just started using Quartz on my asp.net core web API project but I can't figure out how to access my database/context on my quartz class that implements IJob, when I try to add the context to my constructor the job doesn't get scheduled anymore and doesn't run at all, although it should run every 5 seconds, here is my old job class which works fine and get scheduled to run fine every 5 seconds:
class BatchJobCheckContract : IJob
{
public async Task Execute(IJobExecutionContext context)
{
var msg = "\n -- Contract checking job executed at : " + DateTime.Now.ToString();
Debug.WriteLine(msg);
}
}
and here is how I changed it to access my database but doesn't work anymore :
class BatchJobCheckContract : IJob
{
private readonly RHPDbContext _context;
public BatchJobCheckContract(RHPDbContext context)
{
_context = context;
}
public async Task Execute(IJobExecutionContext context)
{
var msg = "\n -- Contract checking job executed at : " + DateTime.Now.ToString();
Debug.WriteLine(msg);
var workers = await _context.Workers
.Include(w => w.ContactInformationGroup.ContactInformations)
.Include(w => w.AddressGroup.Addresses)
.Include(w => w.CalculationProfilGroup.CalculationProfil)
.Include(w => w.Contracts)
.Include(w => w.Absences).ThenInclude(x => x.AbsenceGroup)
.ToListAsync();
Debug.WriteLine(workers.Count);
}
}
what am I doing wrong? how can I access the context the right way?
Quartz.NET has built-in support for hosting on ASP.NET Core. Please have a look at the documentation how to configure your setup to utilize dependency injection.
Related
I have an application that normally should be a simple console application to be programmed as a scheduled task from time to time called by the windows task scheduler.
The program should launch some updates on two databases, one service per one database. Say ContosoDatabase should be updated by the ContosoService.
Finally it was written as an .NET Core app using, and maybe is not the best choice, the IHostedServices as base for the service, like this:
public class ContosoService : IHostedService {
private readonly ILogger<ContosoService> _log;
private readonly IContosoRepository _repository;
private Task executingTask;
public ContosoService(
ILogger<ContosoService> log,
IContosoRepository repository,
string mode) {
_log = log;
_repository = repository;
}
public Task StartAsync(CancellationToken cancellationToken) {
_log.LogInformation(">>> {serviceName} started <<<", nameof(ContosoService));
executingTask = ExcecuteAsync(cancellationToken);
// If the task is completed then return it,
// this should bubble cancellation and failure to the caller
if (executingTask.IsCompleted)
return executingTask;
// Otherwise it's running
// >> don't want it to run!
// >> it should end after all task finished!
return Task.CompletedTask;
}
private async Task<bool> ExcecuteAsync(CancellationToken cancellationToken) {
var myUsers = _repository.GetMyUsers();
if (myUsers == null || myUsers.Count() == 0) {
_log.LogWarning("{serviceName} has any entry to process, will stop", this.GetType().Name);
return false;
}
else {
// on mets à jour la liste des employés Agresso obtenue
await _repository.UpdateUsersAsync(myUsers);
}
_log.LogInformation(">>> {serviceName} finished its tasks <<<", nameof(ContosoService));
return true;
}
public Task StopAsync(CancellationToken cancellationToken) {
_log.LogInformation(">>> {serviceName} stopped <<<", nameof(ContosoService));
return Task.CompletedTask;
}
}
and I call it from main like this:
public static void Main(string[] args)
{
try {
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex) {
Log.Fatal(ex, ">>> the application could not start <<<");
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host
.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) => {
var config = hostContext.Configuration;
if (args.Contains("Alonso")) {
services
.AddHostedService(provider =>
new AlonsoService(
provider.GetService<ILogger<AlonsoService>>(),
provider.GetService<IAlonsoRepository>()));
}
// if there also Cedig in the list, they can be run in parallel
if (args.Contains("Contoso")) {
services
.AddHostedService(provider =>
new ContosoService(
provider.GetService<ILogger<ContosoService>>(),
provider.GetService<IContosoRepository>()));
}
});
Now, the problem, is surely, that the application will not stop once all updates finished.
Is there a way to quickly rewrite the application in order to make it stop after the second service finishes its tasks?
I tried to put the Environment.Exit(0); at the end
public static void Main(string[] args) {
try {
CreateHostBuilder(filteredArgs.ToArray()).Build().Run();
}
catch (Exception ex) {
//Log....
}
Environment.Exit(0); // here
}
but it does not seem to help: the application is still running after all task are completed.
Following #Maxim's suggestion, I found this dirty but working workaround, by injecting the IHostApplicationLifetime and the lastService boolean:
public ConsosoService(
IHostApplicationLifetime hostApplicationLifetime,
// ...
bool lastService)
{ ... }
public async Task StartAsync(CancellationToken cancellationToken)
{
// do the job
if (_lastService)
_hostApplicationLifetime.StopApplication();
// stops the application and cancel/stops other services as well
}
HostedServices are background services. It's the other way around: they can react to application start and stop events, so that they can end gracefully. They are not meant to stop your main application when finished, they potentially live as long as the application does.
I'd say you will be better served with simple Tasks and awaiting all of them. Or send some events when your background jobs finishes its work and handle them in main.
Whatever trigger you may choose you can stop .net app by injecting IHostApplicationLifetime and calling StopApplication() method on it. In earlier versions it's just IApplicationLifetime.
Looking at IHost Interface documentation the method run() does not stop until the host is shutdown. seems that StopAsync() did not stop the service. so Environment.Exit(0); was never reached. maybe use CancellationToken to forcefully end the host, or inject Environment.Exit(0); in ContosoService class if possible even though not optimal.
Here is another approach without need for creating hosted service
using var host = CreateHostBuilder(args).Build();
await host.StartAsync();
using var scope = host.Services.CreateScope();
var worker = scope.ServiceProvider.GetService<Worker>();
await worker!.Run();
await host.StopAsync();
IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices(services => ConfigureServices(services));
void ConfigureServices(IServiceCollection services)
{
//main class which does the work
services.AddScoped<Worker>();
//do some DB operations
services.AddScoped<DbCtxt>();
}
Complete code https://github.com/raghavan-mk/dotnet/tree/main/DIInConsole
I am encountering below error while doing load testing in my applicaiton. This error does not appear while doing load testing from postman. Can anyone help me out with this. I don't know what is getting wrong here.
An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
// Service regisered in Startup.cs
services.AddTransient<IExceptionHandlerService, ExceptionHandlerService>();
// Calling the below method like this
return exceptionHandler.LogRequestAsync(log);
// LogRequestAsync method inside ExceptionHandlerService class is
// throwing this error
public class ExceptionHandlerService : IExceptionHandlerService
{
private ConnectionStrings _connectionStrings;
private myapp_myappContext dbContext;
public ExceptionHandlerService(IOptions<ConnectionStrings> connectionStrings)
{
_connectionStrings = connectionStrings.Value;
dbContext = new myapp_myappContext(_connectionStrings);
}
// This method is throwing error. This method was not async earlier.
// I made this async due to the above error but still,
// the result is the same.
public async Task<int> LogRequestAsync(RequestLogDto model)
{
try
{
var log = new MyappRequestlog()
{
Ipaddress = model.Ipaddress,
UserId = model.UserId,
Url = model.Url,
MethodType = model.MethodType,
Urlreferrer = model.Urlreferrer,
Browser = model.Browser,
Device = model.Device,
RequestBody = model.RequestBody,
Response = model.Response,
ExceptionMessage = model.ExceptionMessage,
StackTrace = model.StackTrace,
CreatedDate = DateTime.Now
};
dbContext = new myapp_myappContext(_connectionStrings);
await dbContext.MyappRequestlog.AddAsync(log);
await dbContext.SaveChangesAsync();
return log.LogId;
}
catch (Exception ex)
{
string strFilePath = ApplicationVariables.ErrorFilePath;
if (!string.IsNullOrEmpty(strFilePath))
{
File.AppendAllLines(strFilePath, new[] { $"{ex}. Error generate datetime : {DateTime.Now}" });
}
return 0;
}
}
public virtual DbSet<MyappExternalcalllog> MyappExternalcalllog { get; set; }
// This is OnConfiguringmethod which the error is indicating
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseMySql(_connectionStrings.DefaultConnection);
}
}
// This method is saving data in database
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyappRequestlog>(entity =>
{
entity.HasKey(e => e.LogId);
entity.ToTable("myapp_requestlog");
entity.Property(e => e.LogId).HasColumnType("int(11)");
entity.Property(e => e.Browser).HasColumnType("varchar(1000)");
entity.Property(e => e.CreatedDate).HasColumnType("datetime");
entity.Property(e => e.Device).HasColumnType("varchar(15)");
entity.Property(e => e.ExceptionMessage).HasColumnType("text");
entity.Property(e => e.Ipaddress)
.HasColumnName("IPAddress")
.HasColumnType("varchar(25)");
entity.Property(e => e.MethodType).HasColumnType("varchar(9)");
entity.Property(e => e.RequestBody).HasColumnType("text");
entity.Property(e => e.Response).HasColumnType("text");
entity.Property(e => e.StackTrace).HasColumnType("text");
entity.Property(e => e.Url)
.HasColumnName("URL")
.HasColumnType("text");
entity.Property(e => e.Urlreferrer)
.HasColumnName("URLReferrer")
.HasColumnType("text");
entity.Property(e => e.UserId).HasColumnType("varchar(38)");
});
}
// Following is the stack trace for this error
System.InvalidOperationException: An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.EntryWithoutDetectChanges[TEntity](TEntity entity)
at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState)
at MyApp.LoggerService.ExceptionHandlerService.LogRequest(RequestLogDto model) in D:\My API and Application TFS\My API\WebAPICore\MyAPI_V2\MyApp.LoggerService\ExceptionHandlerService.cs:line 47. Error generate datetime : 07/07/2020 10:41:26
System.InvalidOperationException: An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.EntryWithoutDetectChanges[TEntity](TEntity entity)
at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState)
at MyApp.LoggerService.ExceptionHandlerService.LogRequest(RequestLogDto model) in D:\My API and Application TFS\My API\WebAPICore\MyAPI_V2\MyApp.LoggerService\ExceptionHandlerService.cs:line 47. Error generate datetime : 07/07/2020 10:41:26
System.InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList`1 entriesToSave)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at MyApp.LoggerService.ExceptionHandlerService.LogRequest(RequestLogDto model) in D:\My API and Application TFS\My API\WebAPICore\MyAPI_V2\MyApp.LoggerService\ExceptionHandlerService.cs:line 47. Error generate datetime : 07/07/2020 10:41:27
System.InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList`1 entriesToSave)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at MyApp.LoggerService.ExceptionHandlerService.LogRequest(RequestLogDto model) in D:\My API and Application TFS\My API\WebAPICore\MyAPI_V2\MyApp.LoggerService\ExceptionHandlerService.cs:line 47. Error generate datetime : 07/07/2020 10:41:27
I've got a .NET core 3.1 app with a hosted service that runs as a console application on Windows.
In case of an error I'm trying to terminate the worker with Environment.Exit(1).
Now the problem is that, if Enviroment.Exit() is called before any await in ExecuteAsync, the application does not terminate. It logs Waiting for the host to be disposed. Ensure all 'IHost' instances are wrapped in 'using' blocks. and then hangs indefinitely.
When I await anything before the call to Enviroment.Exit() it also logs that, but it terminates as expected.
Here is the simplest code that I could come up with to reproduce the problem.
The NotTerminatingWorker hangs forever, the TerminatingWorker terminates. The only difference is a tiny Task.Delay:
public class Program {
public static async Task Main(string[] args) {
using var host = CreateHostBuilder(args).Build();
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) {
return Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) => { services.AddHostedService<NotTerminatingWorker>(); });
}
}
public class NotTerminatingWorker : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
Environment.Exit(1);
}
}
public class TerminatingWorker : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
await Task.Delay(1);
Environment.Exit(1);
}
}
I would expect that both behave the same way, but that's obviously not the case.
Any explanation for this would be greatly appreciated!
UPDATE: The application should be able to run both as a console application and as a Windows service. The non-zero return code is required to get it restarted if it crashes.
And apparently Windows does not restart services that exited with code 0.
I believe the behavior you're seeing is a side-effect of how the .NET Core runtime does its startup: it calls ExecuteAsync for each background worker and then waits for it to complete. So a synchronous ExecuteAsync can cause problems. I've used Task.Run to work around this.
In case of an error I'm trying to terminate the worker with Environment.Exit(1).
I recommend not using Environment.Exit at all. Instead, do a controlled shutdown by injecting IHostApplicationLifetime and calling StopApplication. This will trigger the stoppingToken for each of your background services, and if they ignore it, they will be forcibly terminated after a timeout.
Handling the hostLifetime events in the Main method did for me the job. This is working for me on .NET6
public static int Main(string[] args)
{
ExitCode = 0;
ILogger? logger = null;
try
{
var builder = CreateHostBuilder(args)
.Build();
var hostLifetime = builder.Services.GetRequiredService<IHostApplicationLifetime>();
logger = builder.Services.GetService<ILogger<Program>>();
// register on hostLifetime events for handling stopping and finalize
using var hostLtAppStopping = hostLifetime.ApplicationStopping.Register(() =>
{
// service is about to stop... do some cleanup stuff here
});
using var hostLtAppStopped = hostLifetime.ApplicationStopped.Register(() =>
{
logger?.LogDebug("Service graceful shout down, exit with code {exitCode}!", ExitCode);
Environment.Exit(ExitCode); // ExitCode is set by the caller of hostApplicationLifetime.StopApplication
});
// start the service
logger?.LogDebug("builder.Run()");
builder.Run();
}
catch (Exception e)
{
logger?.LogError(e, "Unhandled Exception occurred => exit with exit code 1!");
ExitCode = 1;
return ExitCode;
}
return ExitCode;
}
I have written my first .Net core program using the MVC framework on a Ubuntu machine. In the program I am trying to interact with SQLite database. The database CRUD operations work fine when processed through the controller class. However when I tried to operate on the database outside the controller I get following error
"Unhandled Exception: System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'MyDbContext'."
For operation outside the controller class, I created a class named MyDbWatch.cs (in the project root director)
public interface IMyDbWatch { }
public class MyDbWatch : IMyDbWatch
{
private readonly MyDbContext _dbContext;
private static Timer _timer;
private AutoResetEvent _autoEvent = null;
public MyDbWatch(MyDbContext context)
{
_dbContext = context;
_autoEvent = new AutoResetEvent(false);
_timer = new Timer(
callback: async s => await OnTimerEventAsync(s),
state: _autoEvent,
dueTime: 5000,
period: 10000);
}
public async Task OnTimerEventAsync(Object stateInfo)
{
Console.WriteLine("retreiving from db - 1");
var ienStates = from m in _dbContext.IenState select m;
Console.WriteLine("retreiving from db - 2");
var listdb = await ienStates.ToListAsync();
Console.WriteLine("retreiving from db - 3");
}
}
Here how I inject different dependencies in Startup.cs file
public class Startup
{
private MyDbWatch _myDbWatch;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<IpointWebMcmContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("IpointContext")));
services.AddScoped<IMyDbWatch, MyDbWatch>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
IMyDbWatch dbwatch)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
_myDbWatch = dbwatch;
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
The the timer callback function OnTimerEventAsync in MyDbWatch.cs gets called first time and debug text 'retreiving from db - 1' get printed in the console. And after that I get the error
Unhandled Exception: System.ObjectDisposedException: Cannot access a disposed object....
Any help resolve this issue would be highly appreciated. I need this kind of watch on the database to push data to the client through the use of SignalR hub framework (not included in the code yet).
And this would be why statics should be avoided. Virtually every time you have a static like this, some developer is going to trip over it, because they aren't considering how things actually work.
The static keyword isn't magic. You've got a scoped service where you want to persist state (your timer), so you just slap a static on it and call it a day. However, this service uses other scoped services (your context), which now are out of sync with this static timer, i.e. the timer sticks around, but the context doesn't.
First, if you need to maintain state across an application lifetime, you should be using a singleton scope. That frees you from the terror of static. However, then, you'll need to utilize the server-locator pattern to get your context, because you cannot inject a scoped instance into a singleton.
public class MyDbWatch : IMyDbWatch
{
private readonly IServiceProvider _serviceProvider;
private readonly Timer _timer;
private AutoResetEvent _autoEvent = null;
public MyDbWatch(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_autoEvent = new AutoResetEvent(false);
_timer = new Timer(
callback: async s => await OnTimerEventAsync(s),
state: _autoEvent,
dueTime: 5000,
period: 10000);
}
public async Task OnTimerEventAsync(Object stateInfo)
{
using (var scope = _serviceProvider.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
Console.WriteLine("retreiving from db - 1");
var ienStates = from m in context.IenState select m;
Console.WriteLine("retreiving from db - 2");
var listdb = await ienStates.ToListAsync();
Console.WriteLine("retreiving from db - 3");
}
}
}
Then, in ConfigureServices:
services.AddSingleton<IMyDbWatch, MyDbWatch>();
Now, I have no idea what you're actually trying to accomplish with any of this, as your code doesn't make much sense, but the above is the only way you're going to safely be able to do it.
I'm trying to set up Hangfire recurrent jobs on application startup (in Startup.cs of a .NET.Core web application), so that we can be sure that they are always registered. I managed to get it to work for a dummy job but I get a strange error for a more real test job:
System.InvalidOperationException: Expression object should be not null.
at Hangfire.Common.Job.FromExpression(LambdaExpression methodCall, Type explicitType)
at Hangfire.RecurringJob.AddOrUpdate(String recurringJobId, Expression`1 methodCall, Func`1 cronExpression, TimeZoneInfo timeZone, String queue)
at WsApplication.Web.Startup.Startup.ConfigureServices(IServiceCollection services) in C:\Projects\boxman\aspnet-core\src\WsApplication.Web.Host\Startup\Startup.cs:line 163
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
This is what I have in my startup class:
// Hangfire (Enable to use Hangfire instead of default job manager)
services.AddHangfire(config =>
{
config.UseSqlServerStorage(_appConfiguration.GetConnectionString("Default"));
});
JobStorage.Current = new SqlServerStorage(_appConfiguration.GetConnectionString("Default"));
// Register recurring jobs on startup
var sp = services.BuildServiceProvider();
var offHireJob = sp.GetService<UnsetContainerIsBookedPerOffHireJob>();
// Test job 1 - this gets registered fine!
RecurringJob.AddOrUpdate("Test", () => Console.Write("Test"), Cron.Minutely);
// Test job 2 - This line triggers the error!
RecurringJob.AddOrUpdate(UnsetContainerIsBookedPerOffHireJob.JobId, () => offHireJob.Run(), Cron.Minutely);
And this is my actual test job:
public class UnsetContainerIsBookedPerOffHireJob : ApplicationService
{
public readonly static string JobId = nameof(UnsetContainerIsBookedPerOffHireJob);
private readonly IRepository<ContractLineMovement, int> _contractLineMovementRepository;
public UnsetContainerIsBookedPerOffHireJob(
IRepository<ContractLineMovement, int> contractLineMovementRepository
){
_contractLineMovementRepository = contractLineMovementRepository;
}
public void Run()
{
Logger.Debug("Running UnsetContainerIsBookedPerOffHireJob.Run()");
var offHirequery = from contractLineMovement in _contractLineMovementRepository.GetAll()
where contractLineMovement.ContractWorkFlowEventId == (int)ContractWorkFlowEventValues.OffHire
select contractLineMovement;
foreach (var offHire in offHirequery)
{
Logger.Debug("Processing offhire with ID: " + offHire.Id + " and Date: " + offHire.HireDate);
}
}
}
I've found the piece of code in Hangfire's source code which raises this exception but I still don't really understand why this expression
() => offHireJob.Run()
would cause a problem. It looks basic to me. Maybe I am missing something basic?
Or is there a better way to register my recurrent jobs once at an application level?