In my project I have to use Quartz but I don't know what i do wrong.
JobFactory:
public class IoCJobFactory : IJobFactory
{
private readonly IServiceProvider _factory;
public IoCJobFactory(IServiceProvider factory)
{
_factory = factory;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return _factory.GetService(bundle.JobDetail.JobType) as IJob;
}
public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}
QuartzExtensions:
public static class QuartzExtensions
{
public static void UseQuartz(this IApplicationBuilder app)
{
app.ApplicationServices.GetService<IScheduler>();
}
public static async void AddQuartz(this IServiceCollection services)
{
var props = new NameValueCollection
{
{"quartz.serializer.type", "json"}
};
var factory = new StdSchedulerFactory(props);
var scheduler = await factory.GetScheduler();
var jobFactory = new IoCJobFactory(services.BuildServiceProvider());
scheduler.JobFactory = jobFactory;
await scheduler.Start();
services.AddSingleton(scheduler);
}
}
And when I try run my Job (class have dependency injection) i always get Exception becouse:
_factory.GetService(bundle.JobDetail.JobType) as IJob;
is always null.
My class implement IJob and in startup.cs I add:
services.AddScoped<IJob, HelloJob>();
services.AddQuartz();
and
app.UseQuartz();
I using standard .net Core dependency injection:
using Microsoft.Extensions.DependencyInjection;
This is just a simple sample of my solution to solve IoC problem:
JobFactory.cs
public class JobFactory : IJobFactory
{
protected readonly IServiceProvider Container;
public JobFactory(IServiceProvider container)
{
Container = container;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return Container.GetService(bundle.JobDetail.JobType) as IJob;
}
public void ReturnJob(IJob job)
{
(job as IDisposable)?.Dispose();
}
}
Startup.cs
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
IApplicationLifetime lifetime,
IServiceProvider container)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMvc();
// the following 3 lines hook QuartzStartup into web host lifecycle
var quartz = new QuartzStartup(container);
lifetime.ApplicationStarted.Register(quartz.Start);
lifetime.ApplicationStopping.Register(quartz.Stop);
}
QuartzStartup.cs
public class QuartzStartup
{
private IScheduler _scheduler; // after Start, and until shutdown completes, references the scheduler object
private readonly IServiceProvider container;
public QuartzStartup(IServiceProvider container)
{
this.container = container;
}
// starts the scheduler, defines the jobs and the triggers
public void Start()
{
if (_scheduler != null)
{
throw new InvalidOperationException("Already started.");
}
var schedulerFactory = new StdSchedulerFactory();
_scheduler = schedulerFactory.GetScheduler().Result;
_scheduler.JobFactory = new JobFactory(container);
_scheduler.Start().Wait();
var voteJob = JobBuilder.Create<VoteJob>()
.Build();
var voteJobTrigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(s => s
.WithIntervalInSeconds(60)
.RepeatForever())
.Build();
_scheduler.ScheduleJob(voteJob, voteJobTrigger).Wait();
}
// initiates shutdown of the scheduler, and waits until jobs exit gracefully (within allotted timeout)
public void Stop()
{
if (_scheduler == null)
{
return;
}
// give running jobs 30 sec (for example) to stop gracefully
if (_scheduler.Shutdown(waitForJobsToComplete: true).Wait(30000))
{
_scheduler = null;
}
else
{
// jobs didn't exit in timely fashion - log a warning...
}
}
}
consider that you should register your service into the container (in my case VoteJob) in advance.
I implement this based on this answer.
I hope it can be helpful.
This is how I did it in my application. Instead of adding the Scheduler to the ioc I only add the factory
services.AddTransient<IJobFactory, AspJobFactory>(
(provider) =>
{
return new AspJobFactory( provider );
} );
My job factory pretty much looks the same. Transient does not really matter as I only use this once anyway. My use Quartz extension method then is
public static void UseQuartz(this IApplicationBuilder app, Action<Quartz> configuration)
{
// Job Factory through IOC container
var jobFactory = (IJobFactory)app.ApplicationServices.GetService( typeof( IJobFactory ) );
// Set job factory
Quartz.Instance.UseJobFactory( jobFactory );
// Run configuration
configuration.Invoke( Quartz.Instance );
// Run Quartz
Quartz.Start();
}
The Quartz class is Singleton as well.
I got the same issue.
I update from
services.AddScoped<IJob, HelloJob>();
to
services.AddScoped<HelloJob>();
then it works.
_factory.GetService(bundle.JobDetail.JobType) as IJob; will not be null :)
Quartz.NET 3.1 will include official support for Microsoft DI and ASP.NET Core Hosted Services.
You can find the revisited packages as:
Quartz.Extensions.DependencyInjection - Microsoft DI integration
Quartz.AspNetCore - ASP.NET Core integration
The best resource the see the new DI integration in progress is to head to the example ASP.NET Core application.
https://www.quartz-scheduler.net/2020/07/08/quartznet-3-1-beta-1-released/
Related
.NET Core 2.1 introduced new Generic Host, which allows to host non-HTTP workloads with all benefits of Web Host. Currently, there is no much information and recipes with it, but I used following articles as a starting point:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.1
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1
https://learn.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/background-tasks-with-ihostedservice
My .NET Core application starts, listens for new requests via RabbitMQ message broker and shuts down by user request (usually by Ctrl+C in console). However, shutdown is not graceful - application still have unfinished background threads while it returns control to OS. I see it by console messages - when I press Ctrl+C in console I see few lines of console output from my application, then OS command prompt and then again console output from my application.
Here is my code:
Program.cs
public class Program
{
public static async Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureHostConfiguration(config =>
{
config.SetBasePath(AppContext.BaseDirectory);
config.AddEnvironmentVariables(prefix: "ASPNETCORE_");
config.AddJsonFile("hostsettings.json", optional: true);
})
.ConfigureAppConfiguration((context, config) =>
{
var env = context.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
config.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
if (env.IsProduction())
config.AddDockerSecrets();
config.AddEnvironmentVariables();
})
.ConfigureServices((context, services) =>
{
services.AddLogging();
services.AddHostedService<WorkerPoolHostedService>();
// ... other services
})
.ConfigureLogging((context, logging) =>
{
if (context.HostingEnvironment.IsDevelopment())
logging.AddDebug();
logging.AddSerilog(dispose: true);
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(context.Configuration)
.CreateLogger();
})
.UseConsoleLifetime()
.Build();
await host.RunAsync();
}
}
WorkerPoolHostedService.cs
internal class WorkerPoolHostedService : IHostedService
{
private IList<VideoProcessingWorker> _workers;
private CancellationTokenSource _stoppingCts = new CancellationTokenSource();
protected WorkerPoolConfiguration WorkerPoolConfiguration { get; }
protected RabbitMqConfiguration RabbitMqConfiguration { get; }
protected IServiceProvider ServiceProvider { get; }
protected ILogger<WorkerPoolHostedService> Logger { get; }
public WorkerPoolHostedService(
IConfiguration configuration,
IServiceProvider serviceProvider,
ILogger<WorkerPoolHostedService> logger)
{
this.WorkerPoolConfiguration = new WorkerPoolConfiguration(configuration);
this.RabbitMqConfiguration = new RabbitMqConfiguration(configuration);
this.ServiceProvider = serviceProvider;
this.Logger = logger;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var connectionFactory = new ConnectionFactory
{
AutomaticRecoveryEnabled = true,
UserName = this.RabbitMqConfiguration.Username,
Password = this.RabbitMqConfiguration.Password,
HostName = this.RabbitMqConfiguration.Hostname,
Port = this.RabbitMqConfiguration.Port,
VirtualHost = this.RabbitMqConfiguration.VirtualHost
};
_workers = Enumerable.Range(0, this.WorkerPoolConfiguration.WorkerCount)
.Select(i => new VideoProcessingWorker(
connectionFactory: connectionFactory,
serviceScopeFactory: this.ServiceProvider.GetRequiredService<IServiceScopeFactory>(),
logger: this.ServiceProvider.GetRequiredService<ILogger<VideoProcessingWorker>>(),
cancellationToken: _stoppingCts.Token))
.ToList();
this.Logger.LogInformation("Worker pool started with {0} workers.", this.WorkerPoolConfiguration.WorkerCount);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
this.Logger.LogInformation("Stopping working pool...");
try
{
_stoppingCts.Cancel();
await Task.WhenAll(_workers.SelectMany(w => w.ActiveTasks).ToArray());
}
catch (AggregateException ae)
{
ae.Handle((Exception exc) =>
{
this.Logger.LogError(exc, "Error while cancelling workers");
return true;
});
}
finally
{
if (_workers != null)
{
foreach (var worker in _workers)
worker.Dispose();
_workers = null;
}
}
}
}
VideoProcessingWorker.cs
internal class VideoProcessingWorker : IDisposable
{
private readonly Guid _id = Guid.NewGuid();
private bool _disposed = false;
protected IConnection Connection { get; }
protected IModel Channel { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected ILogger<VideoProcessingWorker> Logger { get; }
protected CancellationToken CancellationToken { get; }
public VideoProcessingWorker(
IConnectionFactory connectionFactory,
IServiceScopeFactory serviceScopeFactory,
ILogger<VideoProcessingWorker> logger,
CancellationToken cancellationToken)
{
this.Connection = connectionFactory.CreateConnection();
this.Channel = this.Connection.CreateModel();
this.Channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
this.ServiceScopeFactory = serviceScopeFactory;
this.Logger = logger;
this.CancellationToken = cancellationToken;
#region [ Declare ]
// ...
#endregion
#region [ Consume ]
// ...
#endregion
}
// ... worker logic ...
public void Dispose()
{
if (!_disposed)
{
this.Channel.Close(200, "Goodbye");
this.Channel.Dispose();
this.Connection.Close();
this.Connection.Dispose();
this.Logger.LogDebug("Worker {0}: disposed.", _id);
}
_disposed = true;
}
}
So, when I press Ctrl+C I see following output in console (when there is no request processing):
Stopping working pool...
command prompt
Worker id: disposed.
How to shutdown gracefully?
You need IApplicationLifetime. This provides you with all the needed information about application start and shutdown. You can even trigger the shutdown with it via appLifetime.StopApplication();
Look at https://github.com/aspnet/Docs/blob/66916c2ed3874ed9b000dfd1cab53ef68e84a0f7/aspnetcore/fundamentals/host/generic-host/samples/2.x/GenericHostSample/LifetimeEventsHostedService.cs
Snippet(if the link becomes invalid):
public Task StartAsync(CancellationToken cancellationToken)
{
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);
return Task.CompletedTask;
}
I'll share some patterns I think works very well for non-WebHost projects.
namespace MyNamespace
{
public class MyService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly IApplicationLifetime _appLifetime;
public MyService(
IServiceProvider serviceProvider,
IApplicationLifetime appLifetime)
{
_serviceProvider = serviceProvider;
_appLifetime = appLifetime;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_appLifetime.ApplicationStopped.Register(OnStopped);
return RunAsync(stoppingToken);
}
private async Task RunAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
using (var scope = _serviceProvider.CreateScope())
{
var runner = scope.ServiceProvider.GetRequiredService<IMyJobRunner>();
await runner.RunAsync();
}
}
}
public void OnStopped()
{
Log.Information("Window will close automatically in 20 seconds.");
Task.Delay(20000).GetAwaiter().GetResult();
}
}
}
A couple notes about this class:
I'm using the BackgroundService abstract class to represent my service. It's available in the Microsoft.Extensions.Hosting.Abstractions package. I believe this is planned to be in .NET Core 3.0 out of the box.
The ExecuteAsync method needs to return a Task representing the running service. Note: If you have a synchronous service wrap your "Run" method in Task.Run().
If you want to do additional setup or teardown for your service you can inject the app lifetime service and hook into events. I added an event to be fired after the service is fully stopped.
Because you don't have the auto-magic of new scope creation for each web request as you do in MVC projects you have to create your own scope for scoped services. Inject IServiceProvider into the service to do that. All dependencies on the scope should be added to the DI container using AddScoped().
Set up the host in Main( string[] args ) so that it shuts down gracefully when CTRL+C / SIGTERM is called:
IHost host = new HostBuilder()
.ConfigureServices( ( hostContext, services ) =>
{
services.AddHostedService<MyService>();
})
.UseConsoleLifetime()
.Build();
host.Run(); // use RunAsync() if you have access to async Main()
I've found this set of patterns to work very well outside of ASP.NET applications.
Be aware that Microsoft has built against .NET Standard so you don't need to be on .NET Core to take advantage of these new conveniences. If you're working in Framework just add the relevant NuGet packages. The package is built against .NET Standard 2.0 so you need to be on Framework 4.6.1 or above. You can find the code for all of the infrastructure here and feel free to poke around at the implementations for all the abstractions you are working with: https://github.com/aspnet/Extensions
In Startup.cs, you can terminate the application with the Kill() method of the current process:
public void Configure(IHostApplicationLifetime appLifetime)
{
appLifetime.ApplicationStarted.Register(() =>
{
Console.WriteLine("Press Ctrl+C to shut down.");
});
appLifetime.ApplicationStopped.Register(() =>
{
Console.WriteLine("Shutting down...");
System.Diagnostics.Process.GetCurrentProcess().Kill();
});
}
Program.cs
Don't forget to use UseConsoleLifetime() while building the host.
Host.CreateDefaultBuilder(args).UseConsoleLifetime(opts => opts.SuppressStatusMessages = true);
I am trying to use Quartz.Net v3.0.3 and Simple Injector in a windows service.
I have a job class below which i would like to inject some dependencies such as my logger into.
public class JobWorker : IJob
{
private ILogger _logger;
public JobWorker(ILogger logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
return Task.Run(() =>_logger.Log("Do Work"));
}
}
I tried registering Container.Register<IJob, JobWorker>(); on my DI layer but this doesn't help.
If i remove the injected dependency and simply use the default parameterless constructor the job fires correct.
Accord to the post below by Steven, the suggestion is to create a Factory, however the answer provided is out of date in context of the new framework and i'm completely lost as how to inject dependencies into jobs.
Constructor injection with Quartz.NET and Simple Injector
The link provided by #Rabban is still valid and using IServiceProvider is a good design choice, but you can use whatever concrete Container you want.
Here are my 2c based on Rabban's answer using Quartz 3.0.4 and SimpleInjector 4.2.1:
using NLog;
using Quartz;
using Quartz.Spi;
using System;
namespace My.Dear.App.Infrastructure
{
public class SomeJobFactory : IJobFactory
{
private static ILogger logger = LogManager.GetCurrentClassLogger();
private readonly IServiceProvider serviceProvider;
public DexJobFactory(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
try
{
IJobDetail jobDetail = bundle.JobDetail;
Type jobType = jobDetail.JobType;
logger.Debug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}");
return serviceProvider.GetService(jobType) as IJob;
}
catch (Exception ex)
{
logger.Error(ex, Constants.ErrorAt, nameof(IJobFactory.NewJob));
throw new SchedulerException($"Problem instantiating class '{bundle.JobDetail.JobType.FullName}'", ex);
}
}
public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();
}
}
}
Works like a charm for me.
How to get an instance?
public static async Task RegisterQuartz(Container container)
{
ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
IScheduler scheduler = await schedulerFactory.GetScheduler();
IJobFactory jobFactory = new SomeJobFactory(container);
scheduler.JobFactory = jobFactory;
container.RegisterInstance(schedulerFactory);
container.RegisterInstance(jobFactory);
container.RegisterInstance(scheduler);
container.Register<IDearJob, DearJob>();
}
Oh, and don't forget to register your Jobs. Otherwise it may not work.
I suggest creating an Interface for each Job and not using Quartz IJob for that.
public interface IDearJob : IJob { }
public interface DearJob : IDearJob
{
private readonly ISomeService service;
public DearJob(ISomeService service)
{
this.service = service;
}
public async Task Execute(IJobExecutionContext context)
{
// retrieve context if you need
await this.service.DoSomethingAsync(/*params*/);
}
}
Now you can use a breakpoint on Execute.
Cheers.
EDIT
P.S.: Steven answer is very good and I think you can play with it to update that context to yours.
Now serious, cheers.
How can I use .NET Core's default dependency injection in Hangfire?
I am new to Hangfire and searching for an example which works with ASP.NET Core.
See full example on GitHub https://github.com/gonzigonz/HangfireCore-Example.
Live site at http://hangfirecore.azurewebsites.net/
Make sure you have the Core version of Hangfire:
dotnet add package Hangfire.AspNetCore
Configure your IoC by defining a JobActivator. Below is the config for use with the default asp.net core container service:
public class HangfireActivator : Hangfire.JobActivator
{
private readonly IServiceProvider _serviceProvider;
public HangfireActivator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public override object ActivateJob(Type type)
{
return _serviceProvider.GetService(type);
}
}
Next register hangfire as a service in the Startup.ConfigureServices method:
services.AddHangfire(opt =>
opt.UseSqlServerStorage("Your Hangfire Connection string"));
Configure hangfire in the Startup.Configure method. In relationship to your question, the key is to configure hangfire to use the new HangfireActivator we just defined above. To do so you will have to provide hangfire with the IServiceProvider and this can be achieved by just adding it to the list of parameters for the Configure method. At runtime, DI will providing this service for you:
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
IServiceProvider serviceProvider)
{
...
// Configure hangfire to use the new JobActivator we defined.
GlobalConfiguration.Configuration
.UseActivator(new HangfireActivator(serviceProvider));
// The rest of the hangfire config as usual.
app.UseHangfireServer();
app.UseHangfireDashboard();
}
When you enqueue a job, use the registered type which usually is your interface. Don't use a concrete type unless you registered it that way. You must use the type registered with your IoC else Hangfire won't find it.
For Example say you've registered the following services:
services.AddScoped<DbManager>();
services.AddScoped<IMyService, MyService>();
Then you could enqueue DbManager with an instantiated version of the class:
BackgroundJob.Enqueue(() => dbManager.DoSomething());
However you could not do the same with MyService. Enqueuing with an instantiated version would fail because DI would fail as only the interface is registered. In this case you would enqueue like this:
BackgroundJob.Enqueue<IMyService>( ms => ms.DoSomething());
DoritoBandito's answer is incomplete or deprecated.
public class EmailSender {
public EmailSender(IDbContext dbContext, IEmailService emailService)
{
_dbContext = dbContext;
_emailService = emailService;
}
}
Register services:
services.AddTransient<IDbContext, TestDbContext>();
services.AddTransient<IEmailService, EmailService>();
Enqueue:
BackgroundJob.Enqueue<EmailSender>(x => x.Send(13, "Hello!"));
Source:
http://docs.hangfire.io/en/latest/background-methods/passing-dependencies.html
Note: if you want a full sample, see my blog post on this.
All of the answers in this thread are wrong/incomplete/outdated. Here's an example with ASP.NET Core 3.1 and Hangfire.AspnetCore 1.7.
Client:
//...
using Hangfire;
// ...
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddHangfire(config =>
{
// configure hangfire per your requirements
});
}
}
public class SomeController : ControllerBase
{
private readonly IBackgroundJobClient _backgroundJobClient;
public SomeController(IBackgroundJobClient backgroundJobClient)
{
_backgroundJobClient = backgroundJobClient;
}
[HttpPost("some-route")]
public IActionResult Schedule([FromBody] SomeModel model)
{
_backgroundJobClient.Schedule<SomeClass>(s => s.Execute(model));
}
}
Server (same or different application):
{
//...
services.AddScoped<ISomeDependency, SomeDependency>();
services.AddHangfire(hangfireConfiguration =>
{
// configure hangfire with the same backing storage as your client
});
services.AddHangfireServer();
}
public interface ISomeDependency { }
public class SomeDependency : ISomeDependency { }
public class SomeClass
{
private readonly ISomeDependency _someDependency;
public SomeClass(ISomeDependency someDependency)
{
_someDependency = someDependency;
}
// the function scheduled in SomeController
public void Execute(SomeModel someModel)
{
}
}
As far as I am aware, you can use .net cores dependency injection the same as you would for any other service.
You can use a service which contains the jobs to be executed, which can be executed like so
var jobId = BackgroundJob.Enqueue(x => x.SomeTask(passParamIfYouWish));
Here is an example of the Job Service class
public class JobService : IJobService
{
private IClientService _clientService;
private INodeServices _nodeServices;
//Constructor
public JobService(IClientService clientService, INodeServices nodeServices)
{
_clientService = clientService;
_nodeServices = nodeServices;
}
//Some task to execute
public async Task SomeTask(Guid subject)
{
// Do some job here
Client client = _clientService.FindUserBySubject(subject);
}
}
And in your projects Startup.cs you can add a dependency as normal
services.AddTransient< IClientService, ClientService>();
Not sure this answers your question or not
Currently, Hangfire is deeply integrated with Asp.Net Core. Install Hangfire.AspNetCore to set up the dashboard and DI integration automatically. Then, you just need to define your dependencies using ASP.NET core as always.
If you are trying to quickly set up Hangfire with ASP.NET Core (tested in ASP.NET Core 2.2) you can also use Hangfire.MemoryStorage. All the configuration can be performed in Startup.cs:
using Hangfire;
using Hangfire.MemoryStorage;
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(opt => opt.UseMemoryStorage());
JobStorage.Current = new MemoryStorage();
}
protected void StartHangFireJobs(IApplicationBuilder app, IServiceProvider serviceProvider)
{
app.UseHangfireServer();
app.UseHangfireDashboard();
//TODO: move cron expressions to appsettings.json
RecurringJob.AddOrUpdate<SomeJobService>(
x => x.DoWork(),
"* * * * *");
RecurringJob.AddOrUpdate<OtherJobService>(
x => x.DoWork(),
"0 */2 * * *");
}
public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider)
{
StartHangFireJobs(app, serviceProvider)
}
Of course, everything is store in memory and it is lost once the application pool is recycled, but it is a quick way to see that everything works as expected with minimal configuration.
To switch to SQL Server database persistence, you should install Hangfire.SqlServer package and simply configure it instead of the memory storage:
services.AddHangfire(opt => opt.UseSqlServerStorage(Configuration.GetConnectionString("Default")));
I had to start HangFire in main function. This is how I solved it:
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var serviceScope = host.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
try
{
var liveDataHelper = services.GetRequiredService<ILiveDataHelper>();
var justInitHangfire = services.GetRequiredService<IBackgroundJobClient>();
//This was causing an exception (HangFire is not initialized)
RecurringJob.AddOrUpdate(() => liveDataHelper.RePopulateAllConfigDataAsync(), Cron.Daily());
// Use the context here
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "Can't start " + nameof(LiveDataHelper));
}
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Actually there is an easy way for dependency injection based job registration.
You just need to use the following code in your Startup:
public class Startup {
public void Configure(IApplicationBuilder app)
{
var factory = app.ApplicationServices
.GetService<IServiceScopeFactory>();
GlobalConfiguration.Configuration.UseActivator(
new Hangfire.AspNetCore.AspNetCoreJobActivator(factory));
}
}
However i personally wanted a job self registration including on demand jobs (recurring jobs which are never executed, except by manual trigger on hangfire dashboard), which was a little more complex then just that. I was (for example) facing issues with the job service activation, which is why i decided to share most of my implementation code.
//I wanted an interface to declare my jobs, including the job Id.
public interface IBackgroundJob {
string Id { get; set; }
void Invoke();
}
//I wanted to retrieve the jobs by id. Heres my extension method for that:
public static IBackgroundJob GetJob(
this IServiceProvider provider,
string jobId) => provider
.GetServices<IBackgroundJob>()
.SingleOrDefault(j => j.Id == jobId);
//Now i needed an invoker for these jobs.
//The invoker is basically an example of a dependency injected hangfire job.
internal class JobInvoker {
public JobInvoker(IServiceScopeFactory factory) {
Factory = factory;
}
public IServiceScopeFactory Factory { get; }
public void Invoke(string jobId)
{
//hangfire jobs should always be executed within their own scope.
//The default AspNetCoreJobActivator should technically already do that.
//Lets just say i have trust issues.
using (var scope = Factory.CreateScope())
{
scope.ServiceProvider
.GetJob(jobId)?
.Invoke();
}
}
//Now i needed to tell hangfire to use these jobs.
//Reminder: The serviceProvider is in IApplicationBuilder.ApplicationServices
public static void RegisterJobs(IServiceProvider serviceProvider) {
var factory = serviceProvider.GetService();
GlobalConfiguration.Configuration.UseActivator(new Hangfire.AspNetCore.AspNetCoreJobActivator(factory));
var manager = serviceProvider.GetService<IRecurringJobManager>();
var config = serviceProvider.GetService<IConfiguration>();
var jobs = serviceProvider.GetServices<IBackgroundJob>();
foreach (var job in jobs) {
var jobConfig = config.GetJobConfig(job.Id);
var schedule = jobConfig?.Schedule; //this is a cron expression
if (String.IsNullOrWhiteSpace(schedule))
schedule = Cron.Never(); //this is an on demand job only!
manager.AddOrUpdate(
recurringJobId: job.Id,
job: GetJob(job.Id),
cronExpression: schedule);
}
//and last but not least...
//My Method for creating the hangfire job with injected job id
private static Job GetJob(string jobId)
{
var type = typeof(JobInvoker);
var method = type.GetMethod("Invoke");
return new Job(
type: type,
method: method,
args: jobId);
}
Using the above code i was able to create hangfire job services with full dependency injection support. Hope it helps someone.
Use the below code for Hangfire configuration
using eForms.Core;
using Hangfire;
using Hangfire.SqlServer;
using System;
using System.ComponentModel;
using System.Web.Hosting;
namespace eForms.AdminPanel.Jobs
{
public class JobManager : IJobManager, IRegisteredObject
{
public static readonly JobManager Instance = new JobManager();
//private static readonly TimeSpan ZeroTimespan = new TimeSpan(0, 0, 10);
private static readonly object _lockObject = new Object();
private bool _started;
private BackgroundJobServer _backgroundJobServer;
private JobManager()
{
}
public int Schedule(JobInfo whatToDo)
{
int result = 0;
if (!whatToDo.IsRecurring)
{
if (whatToDo.Delay == TimeSpan.Zero)
int.TryParse(BackgroundJob.Enqueue(() => Run(whatToDo.JobId, whatToDo.JobType.AssemblyQualifiedName)), out result);
else
int.TryParse(BackgroundJob.Schedule(() => Run(whatToDo.JobId, whatToDo.JobType.AssemblyQualifiedName), whatToDo.Delay), out result);
}
else
{
RecurringJob.AddOrUpdate(whatToDo.JobType.Name, () => RunRecurring(whatToDo.JobType.AssemblyQualifiedName), Cron.MinuteInterval(whatToDo.Delay.TotalMinutes.AsInt()));
}
return result;
}
[DisplayName("Id: {0}, Type: {1}")]
[HangFireYearlyExpirationTime]
public static void Run(int jobId, string jobType)
{
try
{
Type runnerType;
if (!jobType.ToType(out runnerType)) throw new Exception("Provided job has undefined type");
var runner = runnerType.CreateInstance<JobRunner>();
runner.Run(jobId);
}
catch (Exception ex)
{
throw new JobException($"Error while executing Job Id: {jobId}, Type: {jobType}", ex);
}
}
[DisplayName("{0}")]
[HangFireMinutelyExpirationTime]
public static void RunRecurring(string jobType)
{
try
{
Type runnerType;
if (!jobType.ToType(out runnerType)) throw new Exception("Provided job has undefined type");
var runner = runnerType.CreateInstance<JobRunner>();
runner.Run(0);
}
catch (Exception ex)
{
throw new JobException($"Error while executing Recurring Type: {jobType}", ex);
}
}
public void Start()
{
lock (_lockObject)
{
if (_started) return;
if (!AppConfigSettings.EnableHangFire) return;
_started = true;
HostingEnvironment.RegisterObject(this);
GlobalConfiguration.Configuration
.UseSqlServerStorage("SqlDbConnection", new SqlServerStorageOptions { PrepareSchemaIfNecessary = false })
//.UseFilter(new HangFireLogFailureAttribute())
.UseLog4NetLogProvider();
//Add infinity Expiration job filter
//GlobalJobFilters.Filters.Add(new HangFireProlongExpirationTimeAttribute());
//Hangfire comes with a retry policy that is automatically set to 10 retry and backs off over several mins
//We in the following remove this attribute and add our own custom one which adds significant backoff time
//custom logic to determine how much to back off and what to to in the case of fails
// The trick here is we can't just remove the filter as you'd expect using remove
// we first have to find it then save the Instance then remove it
try
{
object automaticRetryAttribute = null;
//Search hangfire automatic retry
foreach (var filter in GlobalJobFilters.Filters)
{
if (filter.Instance is Hangfire.AutomaticRetryAttribute)
{
// found it
automaticRetryAttribute = filter.Instance;
System.Diagnostics.Trace.TraceError("Found hangfire automatic retry");
}
}
//Remove default hangefire automaticRetryAttribute
if (automaticRetryAttribute != null)
GlobalJobFilters.Filters.Remove(automaticRetryAttribute);
//Add custom retry job filter
GlobalJobFilters.Filters.Add(new HangFireCustomAutoRetryJobFilterAttribute());
}
catch (Exception) { }
_backgroundJobServer = new BackgroundJobServer(new BackgroundJobServerOptions
{
HeartbeatInterval = new System.TimeSpan(0, 1, 0),
ServerCheckInterval = new System.TimeSpan(0, 1, 0),
SchedulePollingInterval = new System.TimeSpan(0, 1, 0)
});
}
}
public void Stop()
{
lock (_lockObject)
{
if (_backgroundJobServer != null)
{
_backgroundJobServer.Dispose();
}
HostingEnvironment.UnregisterObject(this);
}
}
void IRegisteredObject.Stop(bool immediate)
{
Stop();
}
}
}
Admin Job Manager
public class Global : System.Web.HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
if (Core.AppConfigSettings.EnableHangFire)
{
JobManager.Instance.Start();
new SchedulePendingSmsNotifications().Schedule(new Core.JobInfo() { JobId = 0, JobType = typeof(SchedulePendingSmsNotifications), Delay = TimeSpan.FromMinutes(1), IsRecurring = true });
}
}
protected void Application_End(object sender, EventArgs e)
{
if (Core.AppConfigSettings.EnableHangFire)
{
JobManager.Instance.Stop();
}
}
}
I'm trying to use TopShelf together with Quartz.net and Autofac. The code I have below works just fine. However, this line:
cfg.UsingQuartzJobFactory(() => container.Resolve<IJobFactory>());
seems like the wrong way of doing things. Is there a better way of telling Topshelf to use the custom autofac jobfactory? What lifetime scope will the jobfactory have? I'm concerned this line of code is going to cause me some headaches sometime in the future. How do I release the jobfactory when it's no longer needed? Is this line okay as-is?
class Poller : IJob
{
private readonly ILogger _log;
public Poller(ILogger log)
{
_log = log;
_log.Info("Instantiating...");
}
public void Execute(IJobExecutionContext context)
{
_log.Info("Executing...");
}
}
class Program
{
static Autofac.IContainer BuildContainer()
{
var builder = new ContainerBuilder();
builder.RegisterModule<NLogModule>();
builder.RegisterModule<QuartzAutofacFactoryModule>();
builder.RegisterModule(new QuartzAutofacJobsModule(typeof(Poller).Assembly));
var container = builder.Build();
return container;
}
static void Main(string[] args)
{
var container = BuildContainer();
HostFactory.Run(cfg =>
{
cfg.UseNLog();
cfg.UseAutofacContainer(container);
cfg.SetDescription("DESCRIPTION");
cfg.SetDisplayName("DISPLAY");
cfg.SetServiceName("NAME");
cfg.UsingQuartzJobFactory(() => container.Resolve<IJobFactory>());
cfg.ScheduleQuartzJobAsService(q =>
{
q.WithJob(() => JobBuilder.Create<Poller>().Build());
q.AddTrigger(() => TriggerBuilder.Create().WithSimpleSchedule(b => b.WithIntervalInSeconds(20).RepeatForever()).Build());
});
cfg.StartAutomatically();
cfg.RunAsLocalSystem();
});
}
}
For reference: TopShelf.Quartz.ScheduleHobHostConfiguratorExtensions
Also reference: Autofac.Extras.Quartz.QuartzAutofacFactoryModule
I think you should initialize quartz Server with container, this example use unity, but I am sure that work with other containers.
try
{
var container = new UnityContainer();
schedulerFactory = CreateSchedulerFactory();
quartzscheduler = GetScheduler();
SyncPost.Initialize.RepositoryConfig(container);
SyncPost.Initialize.AddToSchedulerContextCustomVars(quartzscheduler, container);
quartzscheduler.JobFactory = new JobFactoryInjection(container);
}
catch (Exception e)
{
logger.Error("Server initialization failed:" + e.Message, e);
throw;
}
where JobFactoryInjection implement IJobFactory:
public class JobFactoryInjection : IJobFactory
{
private readonly UnityContainer container = new UnityContainer();
public JobFactoryInjection(UnityContainer container)
{
if (container == null) throw new ArgumentNullException("container", "Container is null");
this.container = container;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) {
// Return job registrated in container
bundle.JobDetail.JobDataMap.Put(SyncUtils.ContextKeyCenterCode, scheduler.Context.Get(SyncUtils.ContextKeyCenterCode));
return (IJob)container.Resolve(bundle.JobDetail.JobType);
}
public void ReturnJob(IJob job) {
}
}
About JobFactory lifetime, don't worry about it. From Quartz documentation:
"JobFactory simply activates a new instance of the job class. You may want to create your own implementation of JobFactory to accomplish things such as having your application's IoC or DI container produce/initialize the job instance"
Currently I am writing a service using Quartz.NET to schedule the running of it.
I was wondering if anyone has any experience of using constructor injection with Quartz.NET and Simple Injector.
Below is essentially what I wish to achieve
public class JobImplementation: IJob
{
private readonly IInjectedClass injectedClass;
public JobImplementation(IInjectedClass _injectedClass)
{
injectedClass = _injectedClass
}
public void Execute(IJobExecutionContext _context)
{
//Job code
}
According to this blog post, you would need to implement a custom IJobFactory, like this:
public class SimpleInjectorJobFactory : IJobFactory
{
private readonly Container container;
private readonly Dictionary<Type, InstanceProducer> jobProducers;
public SimpleInjectorJobFactory(
Container container, params Assembly[] assemblies)
{
this.container = container;
// By creating producers, jobs can be decorated.
var transient = Lifestyle.Transient;
this.jobProducers =
container.GetTypesToRegister(typeof(IJob), assemblies).ToDictionary(
type => type,
type => transient.CreateProducer(typeof(IJob), type, container));
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler _)
{
var jobProducer = this.jobProducers[bundle.JobDetail.JobType];
return new AsyncScopedJobDecorator(
this.container, () => (IJob)jobProducer.GetInstance());
}
public void ReturnJob(IJob job)
{
// This will be handled automatically by Simple Injector
}
private sealed class AsyncScopedJobDecorator : IJob
{
private readonly Container container;
private readonly Func<IJob> decorateeFactory;
public AsyncScopedJobDecorator(
Container container, Func<IJob> decorateeFactory)
{
this.container = container;
this.decorateeFactory = decorateeFactory;
}
public async Task Execute(IJobExecutionContext context)
{
using (AsyncScopedLifestyle.BeginScope(this.container))
{
var job = this.decorateeFactory();
await job.Execute(context);
}
}
}
}
Furthermore, you'll need the following registrations:
var container = new Container();
container.Options.ScopedLifestyle = new AsyncScopedLifestyle();
var factory = new StdSchedulerFactory();
IScheduler scheduler = await factory.GetScheduler();
scheduler.JobFactory = new SimpleInjectorJobFactory(
container,
Assembly.GetExecutingAssembly()); // assemblies that contain jobs
// Optional: register some decorators
container.RegisterDecorator(typeof(IJob), typeof(LoggingJobDecorator));
container.Verify();
Late to the party, but https://github.com/hbiarge/Quartz.Unity works well for combining Quartz.NET and Unity.
IUnityContainer container = new UnityContainer();
container.AddNewExtension<Quartz.Unity.QuartzUnityExtension>();
// do your other Unity registrations
IScheduler scheduler = container.Resolve<IScheduler>();
scheduler.ScheduleJob(
new JobDetailImpl(myCommandName, typeof(MyCommand)),
TriggerBuilder.Create()
.WithCronSchedule(myCronSchedule)
.StartAt(startTime)
.Build()
);
scheduler.Start();
There are few steps to use Quartz.net with dependency injection engine from asp.net core.
Add nuget package to your project:
Microsoft.Extensions.DependencyInjection
Create custom JobFactory:
public class JobFactory : IJobFactory
{
protected readonly IServiceProvider _serviceProvider;
public JobFactory(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
=> _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;
public void ReturnJob(IJob job)
=> (job as IDisposable)?.Dispose();
}
Specify JobFactory when configuring scheduler:
var scheduler = await StdSchedulerFactory.GetDefaultScheduler();
scheduler.JobFactory = new JobFactory(_serviceProvider);
For someone can be usefull example of win service with Quartz.net and DI (from asp.net core) on the board:
public class WinService : ServiceBase
{
private Scheduler _scheduleManager;
private readonly Startup _startup;
public WinService()
{
ServiceName = "SomeWinService";
_startup = new Startup();
}
static void Main(string[] args)
{
var service = new WinService();
// Working as Windows-service
if (Console.IsInputRedirected && Console.IsOutputRedirected)
{
ServiceBase.Run(service);
}
// Working as console app
else
{
service.OnStart(args);
Console.WriteLine("Press any key to stop...");
Console.ReadKey();
service.OnStop();
}
}
protected override void OnStart(string[] args)
{
_startup.RegisterServices();
_scheduleManager = new Scheduler(_startup.ServiceProvider);
_scheduleManager.StartTracking().Wait();
}
protected override void OnPause()
=> _scheduleManager.PauseTracking().Wait();
protected override void OnContinue()
=> _scheduleManager.ResumeTracking().Wait();
protected override void OnStop()
{
_scheduleManager.StopTracking().Wait();
_startup.DisposeServices();
}
}
public class Startup
{
private IServiceProvider _serviceProvider;
public IServiceProvider ServiceProvider => _serviceProvider;
public void RegisterServices()
{
_serviceProvider = new ServiceCollection()
//.AddTransient(...)
//.AddScoped(...)
//.AddSingleton(...)
.BuildServiceProvider();
}
public void DisposeServices()
{
if (_serviceProvider == null)
return;
if (_serviceProvider is IDisposable)
{
((IDisposable)_serviceProvider).Dispose();
}
}
}
public class Scheduler
{
private readonly IServiceProvider _serviceProvider;
private IScheduler _scheduler;
public Scheduler(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
public async Task StartTracking()
{
_scheduler = await StdSchedulerFactory.GetDefaultScheduler();
_scheduler.JobFactory = new JobFactory(_serviceProvider);
await _scheduler.Start();
// Schedule your jobs here
}
public async Task PauseTracking() => await _scheduler?.PauseAll();
public async Task ResumeTracking() => await _scheduler?.ResumeAll();
public async Task StopTracking() => await _scheduler?.Shutdown();
}