1

I am trying to inject services into my SendEmailJob class. I use standard ASP Core dependency injection and Quartz library for scheduling. I am trying to build solution based on this answer. But still I am facing injecting issues.

I have such code setup:

//Startup.cs, ConfigureServices
ServiceAutoConfig.Configure(allServices);
services.AddScoped<IUnitOfWork, UnitOfWork>();

services.AddTransient<IJobFactory, JobFactory>((provider) => new JobFactory(services.BuildServiceProvider()));
services.AddTransient<SendEmailJob>();

//Startup.cs, Configure
app.UseQuartz((quartz) => quartz.AddJob<SendEmailJob>("SendEmailJob", "Email", mailSettings.EmailSchedulerInterval));

Implementation of SendEmailJob:

public class SendEmailJob : IJob
{
    private readonly IMessageService _messageService;
    private static bool IsBusy = false;

    public SendEmailJob(IMessageService messageService)
    {
        _messageService = messageService;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        try
        {
            if (IsBusy)
                return;
            IsBusy = true;
            //...
        }
        catch (Exception error)
        {
        }
        finally
        {
            IsBusy = false;
        }
    }
}

Implementation of JobFacctory:

public class JobFactory : IJobFactory
{
    protected readonly IServiceProvider _container;

    public JobFactory(IServiceProvider container)
    {
        _container = container;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        try
        {
            var res = _container.GetService(bundle.JobDetail.JobType) as IJob;
            return res;
        }
        catch (Exception ex)
        {
            //ERROR-  Cannot resolve 'Quartz.Jobs.SendEmailJob' from root provider because it 
            //        requires scoped service 'BLL.Base.UnitOfWork.Interfaces.IUnitOfWork'.
            throw;
        }
    }

    public void ReturnJob(IJob job)
    {
        (job as IDisposable)?.Dispose();
    }
} 

Implementation of Quartz.cs

public class Quartz
{
    private IScheduler _scheduler;
    public static IScheduler Scheduler { get { return Instance._scheduler; } }
    private static Quartz _instance = null;

    public static Quartz Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Quartz();
            }
            return _instance;
        }
    }

    private Quartz()
    {
        Init();
    }

    private async void Init()
    {
        _scheduler = await new StdSchedulerFactory().GetScheduler();
    }

    public IScheduler UseJobFactory(IJobFactory jobFactory)
    {
        Scheduler.JobFactory = jobFactory;
        return Scheduler;
    }

    public async void AddJob<T>(string name, string group, int interval)
        where T : IJob
    {
        IJobDetail job = JobBuilder.Create<T>()
            .WithIdentity(name, group)
            .Build();

        ITrigger jobTrigger = TriggerBuilder.Create()
            .WithIdentity(name + "Trigger", group)
            .StartNow()
            .WithSimpleSchedule(t => t.WithIntervalInSeconds(interval).RepeatForever()) // Mit wiederholung alle interval sekunden
            .Build();

        await Scheduler.ScheduleJob(job, jobTrigger);
    }

    public static async void Start()
    {
        await Scheduler.Start();
    }
}

And implementation of UseQuartzExtension:

public static void UseQuartz(this IApplicationBuilder app, Action<Quartz> configuration)
{
    var jobFactory = new JobFactory(app.ApplicationServices);
    Quartz.Instance.UseJobFactory(jobFactory);

    configuration.Invoke(Quartz.Instance);
    Quartz.Start();
}

And there is an error while injecting IMessageService into SendMailJob. Because it requires UnitOfWork or fails on any other scoped service.

Could you please explain me how to inject it correct?

ovasylenko
  • 2,635
  • 4
  • 17
  • 29

2 Answers2

2

The problem is that you register IUnitOfWork as scoped, but you don't have any scope at the moment when you resolve it. Create it before resolving your job:

public class JobFactory : IJobFactory, IDisposable
{
    protected readonly IServiceScope _scope;

    public JobFactory(IServiceProvider container)
    {
        _scope = container.CreateScope();
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        var res = _scope.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob;
        return res;
    }

    public void ReturnJob(IJob job)
    {
        (job as IDisposable)?.Dispose();
    }

    public void Dispose()
    {
        _scope.Dispose();
    }
} 
Alex Riabov
  • 8,655
  • 5
  • 47
  • 48
  • it helped fix this place, but then in Execute method it throws an error `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: 'ApplicationDbContext'.`. Any ideas how to fix it? – ovasylenko Sep 19 '18 at 13:14
0

I'm not sure if you were able to resolve the disposed DbContext issue you mentioned in your comment, but I'm working on a to be released .NET core app with the same problem and came up with a solution similar to Alex Riabov but uses a concurrent dictionary to dispose the scope when the job has ended. The result is a new dbcontext is injected into my job when a new job is instantiated.

 public class QuartzJobFactory : IJobFactory
{
    protected readonly IServiceProvider serviceProvider;
    private ConcurrentDictionary<IJob, IServiceScope> scopes = new ConcurrentDictionary<IJob, IServiceScope>();

    public QuartzJobFactory(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    // instantiation of new job
    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        try {
            var scope = serviceProvider.CreateScope();
            var job = scope.ServiceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;

            scopes.TryAdd(job, scope);

            return job;
        }
        catch (Exception ex) {
            throw;
        }
    }

    // executes when job is complete
    public void ReturnJob(IJob job)
    {
        try {
            (job as IDisposable)?.Dispose();

            if (scopes.TryRemove(job, out IServiceScope scope))
                scope.Dispose();
        }
        catch (Exception ex) {

        }
    }
}
David Adams
  • 519
  • 6
  • 8