Quartz.net和Ninject:如何使用NInject将实现绑定到我的作业?

4

我目前正在一款ASP.Net MVC 4网页应用中工作,我们使用NInject进行依赖注入。我们还基于Entity framework使用UnitOfWork和Repositories。

我们希望在应用程序中使用Quartz.net来定期启动一些自定义作业。我想让NInject自动绑定我们作业所需的服务。

实现方式可能如下:

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));
    }
}

到目前为止,在我们的NInjectWebCommon中,绑定是这样配置的(使用请求范围):
     kernel.Bind<IDispatchingManagementService>().To<DispatchingManagementService>();

是否可以使用NInject将正确的实现注入到我们的自定义作业中?如何操作?我已经在堆栈溢出上阅读了一些帖子,但我需要一些建议和使用NInject的示例。


请查看https://dev59.com/LILba4cB1Zd3GeqPf4un,了解如何处理Quartz作业中的“UnitOfWork”范围。 - BatteryBackupUnit
4个回答

7

在Quartz调度中使用JobFactory,并在那里解析您的作业实例。

因此,在您的NInject配置中设置作业(我猜测这里是正确的NInject语法)

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

接下来,创建一个JobFactory:[编辑:这是@BatteryBackupUnit的答案的修改版本]

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);
    }
}

然后,当您创建调度程序时,请将JobFactory分配给它:

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

    schedule.JobFactory = new NInjectJobFactory(root);

    return schedule;
}

Quartz将使用JobFactory创建作业,而NInject将为您解决依赖关系。

4

关于 IUnitOfWork 的作用域,根据我链接的 回答中的评论,您可以这样做:

// 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();

有一点需要注意:

在 Web 请求中,如果 async 的使用不正确,可能会导致你错误地在工作线程中解析一个 IUnitOfWork,而此时 HttpContext.Current 为空。如果没有备用绑定,这将会导致异常抛出,提示你做错了什么。但是,如果使用了备用绑定,问题可能会以一种不明显的方式呈现出来。也就是说,有时它可能正常工作,有时却不能正常工作。这是因为对于同一个请求,将会存在两个或更多的 IUnitOfWork 实例。

为了解决这个问题,我们可以让绑定更具体。为此,我们需要一些参数来告诉我们使用另外一个而非 InRequestScope()。请看:

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; }
    }
}

现在请将作业工厂调整为以下内容:
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);
    }
}

并且适应 IUnitOfWork 绑定:

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

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

这样,如果您错误地使用了async,仍然会出现异常,但是对于quartz任务,IUnitOfWork范围仍将起作用。


1

对于可能感兴趣的任何用户,这是最终对我有效的解决方案。

我进行了一些调整以使其与我的项目匹配。请注意,在NewJob方法中,我将Kernel.Get的调用替换为_resolutionRoot.Get。

您可以在此处找到:

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)
    {
    }
}

这是我的工作呼叫表:

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

非常感谢您的帮助。

小改动:你可以将 RegisterScheduler(IKernel kernel) 改为 RegisterScheduler(IResolutionRoot kernel) - BatteryBackupUnit
另外,为了方便未来的读者,您应该将两个答案合并为一个(因为它们描述了相同的解决方案,只是细节有所不同)。此外,您应该接受解决了您的问题的答案(在这种情况下,可能会是您自己的答案;-)。习惯上,对于帮助过您的答案,您也应该点赞(点击帖子左上角数字上方的“向上”按钮)(请参见http://stackoverflow.com/help/why-vote)。 - BatteryBackupUnit

0
非常感谢您的回复。我已经实现了类似的东西,绑定也正常工作:)
    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
            var resolver = DependencyResolver.Current;
            var myJob = (IJob)resolver.GetService(typeof(IJob));
            return myJob;
    }

就像我之前说过的那样,我在我的项目中使用了一个基于EF的服务和工作单元,并且它们都是通过NInject注入的。

public class DispatchingManagementService : IDispatchingManagementService
{
    private readonly IUnitOfWork _unitOfWork;

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

请看这里,我是如何绑定实现的:
kernel.Bind<IUnitOfWork>().To<EfUnitOfWork>()
kernel.Bind<IDispatchingManagementService>().To<DispatchingManagementService>();
kernel.Bind<IJob>().To<DispatchingJob>(); 

总之,IUnitOfWork的绑定是针对以下情况完成的: - 每当一个新请求到达我的应用程序ASP.Net MVC时:请求范围 - 每次运行作业时:InCallScope

根据EF的行为,有哪些最佳实践?我已经找到了使用CallInScope的信息。是否可以告诉NInject在每次新请求到达应用程序时获取ByRequest范围,在每次运行作业时获取InCallScope范围?如何做到这一点?

非常感谢您的帮助


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接