4

I am actually working in an ASP.Net MVC 4 web application where we are using NInject for dependency injection. We are also using UnitOfWork and Repositories based on Entity framework.

We would like to use Quartz.net in our application to start some custom job periodically. I would like that NInject bind automatically the services that we need in our job.

It could be something like this:

public class DispatchingJob : IJob
{
    private readonly IDispatchingManagementService _dispatchingManagementService;

    public DispatchingJob(IDispatchingManagementService dispatchingManagementService )
    {
         _dispatchingManagementService = dispatchingManagementService ;
    }

    public void Execute(IJobExecutionContext context)
    {
         LogManager.Instance.Info(string.Format("Dispatching job started at: {0}", DateTime.Now));
        _dispatchingManagementService.DispatchAtomicChecks();
        LogManager.Instance.Info(string.Format("Dispatching job ended at: {0}", DateTime.Now));
    }
}

So far, in our NInjectWebCommon binding is configured like this (using request scope):

     kernel.Bind<IDispatchingManagementService>().To<DispatchingManagementService>();

Is it possible to inject the correct implementation into our custom job using NInject ? and how to do it ? I have read already few posts on stack overflow, however i need some advises and some example using NInject.

stuartd
  • 70,509
  • 14
  • 132
  • 163
Vannick
  • 75
  • 1
  • 8
  • have a look at http://stackoverflow.com/questions/25339941/ninject-scoping-for-dbcontext-used-in-quartz-net-job to see how to handle `UnitOfWork` scoping in quartz jobs. – BatteryBackupUnit Sep 17 '14 at 14:02

4 Answers4

7

Use a JobFactory in your Quartz schedule, and resolve your job instance there.

So, in your NInject config set up the job (I'm guessing at the correct NInject syntax here)

// Assuming you only have one IJob
kernel.Bind<IJob>().To<DispatchingJob>();

Then, create a JobFactory: [edit: this is a modified version of @BatteryBackupUnit's answer here]

public class NInjectJobFactory : IJobFactory
{
    private readonly IResolutionRoot resolutionRoot;

    public NinjectJobFactory(IResolutionRoot resolutionRoot)
    {
        this.resolutionRoot = resolutionRoot;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        // If you have multiple jobs, specify the name as
        // bundle.JobDetail.JobType.Name, or pass the type, whatever
        // NInject wants..
        return (IJob)this.resolutionRoot.Get<IJob>();
    }

    public void ReturnJob(IJob job)
    {
        this.resolutionRoot.Release(job);
    }
}

Then, when you create the scheduler, assign the JobFactory to it:

private IScheduler GetSchedule(IResolutionRoot root)
{
    var schedule = new StdSchedulerFactory().GetScheduler();

    schedule.JobFactory = new NInjectJobFactory(root);

    return schedule;
}

Quartz will then use the JobFactory to create the job, and NInject will resolve the dependencies for you.

Community
  • 1
  • 1
stuartd
  • 70,509
  • 14
  • 132
  • 163
4

Regarding scoping of the IUnitOfWork, as per a comment of the answer i linked, you can do

// default for web requests
Bind<IUnitOfWork>().To<UnitOfWork>()
    .InRequestScope();

// fall back to `InCallScope()` when there's no web request.
Bind<IUnitOfWork>().To<UnitOfWork>()
    .When(x => HttpContext.Current == null)
    .InCallScope();

There's only one caveat that you should be aware of: With incorrect usage of async in a web request, you may mistakenly be resolving a IUnitOfWork in a worker thread where HttpContext.Current is null. Now without the fallback binding, this would fail with an exception which would show you that you've done something wrong. With the fallback binding however, the issue may present itself in an obscured way. That is, it may work sometimes, but sometimes not. This is because there will be two (or even more) IUnitOfWork instances for the same request.

To remedy this, we can make the binding more specific. For this, we need some parameter to tell us to use another than InRequestScope(). Have a look at:

public class NonRequestScopedParameter : Ninject.Parameters.IParameter
{
    public bool Equals(IParameter other)
    {
        if (other == null)
        {
            return false;
        }

        return other is NonRequestScopedParameter;
    }

    public object GetValue(IContext context, ITarget target)
    {
        throw new NotSupportedException("this parameter does not provide a value");
    }

    public string Name
    {
        get { return typeof(NonRequestScopedParameter).Name; }
    }

    // this is very important
    public bool ShouldInherit
    {
        get { return true; }
    }
}

now adapt the job factory as follows:

public class NInjectJobFactory : IJobFactory
{
    private readonly IResolutionRoot resolutionRoot;

    public NinjectJobFactory(IResolutionRoot resolutionRoot)
    {
        this.resolutionRoot = resolutionRoot;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
         return (IJob) this.resolutionRoot.Get(
                          bundle.JobDetail.JobType,
                          new NonrequestScopedParameter()); // parameter goes here
    }

    public void ReturnJob(IJob job)
    {
        this.resolutionRoot.Release(job);
    }
}

and adapt the IUnitOfWork bindings:

Bind<IUnitOfWork>().To<UnitOfWork>()
    .InRequestScope();

Bind<IUnitOfWork>().To<UnitOfWork>()
    .When(x => x.Parameters.OfType<NonRequestScopedParameter>().Any())
    .InCallScope();

This way, if you use async wrong, there'll still be an exception, but IUnitOfWork scoping will still work for quartz tasks.

Community
  • 1
  • 1
BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
1

For any users that could be interested, here is the solution that finally worked for me.

I have made it working doing some adjustment to match my project. Please note that in the method NewJob, I have replaced the call to Kernel.Get by _resolutionRoot.Get.

As you can find here:

public class JobFactory : IJobFactory
{
    private readonly IResolutionRoot _resolutionRoot;

    public JobFactory(IResolutionRoot resolutionRoot)
    {
        this._resolutionRoot = resolutionRoot;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        try
        {
            return (IJob)_resolutionRoot.Get(
                 bundle.JobDetail.JobType, new NonRequestScopedParameter()); // parameter goes here
        }
        catch (Exception ex)
        {
            LogManager.Instance.Info(string.Format("Exception raised in JobFactory"));
        }
    }

    public void ReturnJob(IJob job)
    {
    }
}

And here is the call schedule my job:

    public static void RegisterScheduler(IKernel kernel)
    {
        try
        {
            var scheduler = new StdSchedulerFactory().GetScheduler();
            scheduler.JobFactory = new JobFactory(kernel);
            ....
        }
    }

Thank you very much for your help

Vannick
  • 75
  • 1
  • 8
  • minor: you could change `RegisterScheduler(IKernel kernel)` to `RegisterScheduler(IResolutionRoot kernel)`. – BatteryBackupUnit Sep 23 '14 at 12:03
  • Also you should consolidate your two answers into one (since they describe the identical solution with a few different details), for the benefit of future readers. Also, you should accept the answer (click the check mark) that solved your problem (this is probably going to be your own answer in this case ;-). It is also customary to upvote (hit the "up" button above the number on the top-left corner of a post) answers that helped you (see http://stackoverflow.com/help/why-vote) – BatteryBackupUnit Sep 23 '14 at 12:33
0

Thanks so much for your response. I have implemented something like that and the binding is working :):

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
            var resolver = DependencyResolver.Current;
            var myJob = (IJob)resolver.GetService(typeof(IJob));
            return myJob;
    }

As I told before I am using in my project a service and unit of work (based on EF) that are both injected with NInject.

public class DispatchingManagementService : IDispatchingManagementService
{
    private readonly IUnitOfWork _unitOfWork;

    public DispatchingManagementService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
}

Please find here how I am binding the implementations:

kernel.Bind<IUnitOfWork>().To<EfUnitOfWork>()
kernel.Bind<IDispatchingManagementService>().To<DispatchingManagementService>();
kernel.Bind<IJob>().To<DispatchingJob>(); 

To resume, the binding of IUnitOfWork is done for: - Eevery time a new request is coming to my application ASP.Net MVC: Request scope - Every time I am running the job: InCallScope

What are the best practices according to the behavior of EF ? I have find information to use CallInScope. Is it possible to tell NInject to get a scope ByRequest everytime a new request is coming to the application, and a InCallScope everytime my job is running ? How to do that ?

Thank you very much for your help

Vannick
  • 75
  • 1
  • 8