Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A Quartz.NET job factory using Microsoft.Extensions.DependencyInjection and scoped services
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Spi;
public class ServiceProviderJobFactory : IJobFactory
{
private readonly IServiceProvider _rootServiceProvider;
private ConcurrentDictionary<Type, IServiceScope> _scopes = new ConcurrentDictionary<Type, IServiceScope>();
public ServiceProviderJobFactory(IServiceProvider rootServiceProvider)
{
_rootServiceProvider = rootServiceProvider ?? throw new ArgumentNullException(rootServiceProvider);
}
public IJob NewJob(TriggerFireBundle bundle, IScheduler scheduler)
{
var jobType = bundle.JobDetail.JobType;
// MA - Generate a scope for the job, this allows the job to be registered
// using .AddScoped<T>() which means we can use scoped dependencies
// e.g. database contexts
var scope = _scopes.GetOrAdd(jobType, t => _rootServiceProvider.CreateScope());
return (IJob)scope.ServiceProvider.GetRequiredService(jobType);
}
public void ReturnJob(IJob job)
{
var jobType = job?.GetType();
if (job != null && _scopes.TryGetValue(jobType, out var scope)
{
// MA - Dispose of the scope, which disposes of the job's dependencies
scope.Dispose();
// MA - Remove the scope so the next time the job is resolved,
// we can get a new job instance
_scopes.TryRemove(jobType, out _);
}
}
}
@Antaris

This comment has been minimized.

Copy link
Owner Author

@Antaris Antaris commented May 21, 2019

One problem with this design is that the cache key type is the Type instance, which means overlapping jobs would resolve to the same IServiceScope instance, which could lead to unexpected disposal of services in subsequent jobs if they do overlap.

Alternatively, instead of using the Type as a cache key, use the job instance itself:

IJob NewJob(...)
{
	var jobType = bundle.JobDetail.JobType;
	var scope = _rootServiceProvider.CreateScope();
	var job = (IJob)scope.ServiceProvider.GetRequiredService(jobType);
	
	_scopes.TryAdd(job, scope);
	return job;
}

void ReturnJob(IJob job)
{
	if (job != null && _scopes.TryGetValue(job, out var scope)
	{
		scope.Dispose();
		_scopes.TryRemove(job, out _);
	}
}
@AndyPook

This comment has been minimized.

Copy link

@AndyPook AndyPook commented May 28, 2019

How about https://github.com/AndyPook/QuartzHostedService/blob/master/QuartzHostedService/ScopedJobFactory.cs as another alternative?
Tightly associates the scope with the job, avoids the dictionary...

@RuslanPr0g

This comment has been minimized.

Copy link

@RuslanPr0g RuslanPr0g commented Feb 19, 2021

Why the factory pattern must be used? Are there other ways to achieve the same result in a much cleaner way?

@Antaris

This comment has been minimized.

Copy link
Owner Author

@Antaris Antaris commented Feb 19, 2021

Quartz now has a baked in solution using their new Quartz.Extensions.Hosting package

https://andrewlock.net/using-quartz-net-with-asp-net-core-and-worker-services/

@RuslanPr0g

This comment has been minimized.

Copy link

@RuslanPr0g RuslanPr0g commented Feb 19, 2021

Quartz now has a baked in solution using their new Quartz.Extensions.Hosting package

https://andrewlock.net/using-quartz-net-with-asp-net-core-and-worker-services/

Thank you, I literally have found that blog at the same time you wrote me :)

@td28354

This comment has been minimized.

Copy link

@td28354 td28354 commented Mar 2, 2021

I checked the provided link about Quartz.Extensions.Hosting but what happens If I need to create jobs based on some information received after the worker service is started.
Let's say I have a BackgroundService hosted in a windows service and this windows service is responsible for creating new jobs based on some information received from other applications, let's say, a desktop application; unfortunately the above link about Quartz.Extensions.Hosting, does not help at all. Please correct me if I'm wrong.

Thanks, AndyPook for the provided sample.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment