I'm trying to setup Hangfire to run a recurring job within Startup.cs
with a standalone method. For this to work, I need to grab some ApplicationServices I have injected already. But the job execution fails with this error:
Recurring job 'Job: Dispatch Email from Queue' can't be scheduled due to an error and will be retried in 00:00:15.
System.InvalidOperationException: Recurring job can't be scheduled, see inner exception for details.
---> Hangfire.Common.JobLoadException: Could not load the job. See inner exception for the details.
---> Newtonsoft.Json.JsonSerializationException: Could not create an instance of type DA.BrrMs.Application.Interfaces.ServiceInterfaces.IBudgetReleaseRequestService. Type is an interface or abstract class and cannot be instantiated.
Here is what I have:
public class Startup
{
private IConfiguration Configuration { get; }
private RecurringJobManager _jobManager;
public Startup(IConfiguration configuration) => Configuration = configuration;
public void ConfigureServices(IServiceCollection services)
{
...
if (Configuration["SystemSettings:UseHangfire"] == "1")
{
services.AddHangfire(c => c.UseMemoryStorage());
JobStorage.Current = new MemoryStorage();
services.AddHangfireServer();
GlobalConfiguration.Configuration.UseMemoryStorage();
}
RegisterServices(services);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
var requestService = app.ApplicationServices.GetService<IBudgetReleaseRequestService>();
var mailService = app.ApplicationServices.GetService<IMailer>();
_jobManager = new RecurringJobManager();
_jobManager.AddOrUpdate("Job: Dispatch Email from Queue", () => StartupMailJob(requestService, mailService), Cron.Minutely);
}
private static void RegisterServices(IServiceCollection services) => DependencyContainer.RegisterServices(services);
public static async Task StartupMailJob(IBudgetReleaseRequestService requestService, IMailer mailService)
{
try
{
var emailsToSend = await requestService.DispatchEmails();
// Send emails
foreach (var emailToSend in emailsToSend)
{
}
// Update status of sent
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
// In a project, far far away...
public static void RegisterServices(IServiceCollection services)
{
...
services.AddScoped<IBudgetReleaseRequestService, BudgetReleaseRequestService>();
...
}
How do I satisfy Hangfire's requirment of a class instance instead of an interface?
Here is the implementation that I found best worked for me:
1 Register the service I wanted to schedule in Startup.cs
services.AddScoped<IMyJob, MYJob>();
2 Created an activator class that is used to define the scope for the execution of the job.
public class HangFireActivatorMyJob : IHangFireActivatorMyJob
{
private readonly IServiceProvider _serviceProvider;
public HangFireActivatorMyJob (IServiceProvider serviceProvider)
{
this._serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
public async Task Run(IJobCancellationToken token)
{
token.ThrowIfCancellationRequested();
await RunAtTimeOf(DateTime.Now);
}
public async Task RunAtTimeOf(DateTime now)
{
using IServiceScope scope = this._serviceProvider.CreateScope();
var myJobService= scope.ServiceProvider.GetRequiredService<IMyJob>();
await myJobService.RunSomeTaskOnJob();
}
}
3 Register the activator
services.AddTransient<IHangFireActivatorMyJob, HangFireActivatorMyJob >();
4 Add the IRecurringJobManager interface to the configure method within Startup.cs
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env,
IRecurringJobManager recurringJobManager)
5 Add the job to the provided IRecurringJobManager
recurringJobManager.AddOrUpdate<HangFireActivatorMyJob >(nameof(HangFireActivatorMyJob ),
job => serviceProvider.GetRequiredService<IHangFireActivatorMyJob>()
.Run(JobCancellationToken.Null)
, Cron.Hourly(3), TimeZoneInfo.Utc);