C# 在.NETCore2中模拟Hangfire重复出现的作业依赖项

C# 在.NETCore2中模拟Hangfire重复出现的作业依赖项,c#,dependency-injection,asp.net-core-2.0,hangfire,C#,Dependency Injection,Asp.net Core 2.0,Hangfire,考虑以下控制器: public class SubmissionController : Controller { public SubmissionController() { } public IActionResult Post() { RecurringJob.AddOrUpdate(() => InitiateSubmission(), Cron.Minutely); return Ok("Periodic s

考虑以下控制器:

public class SubmissionController : Controller
{ 
    public SubmissionController()
    { }

    public IActionResult Post()
    {
        RecurringJob.AddOrUpdate(() => InitiateSubmission(), Cron.Minutely);

        return Ok("Periodic submission triggered");
    }
}
Hangfire是否提供了一个抽象,为RecurringJob类注入了依赖关系?我做了一些研究,唯一可用的抽象是
IBackgroundJobClient
,它没有安排定期作业的选项

我需要验证作业是否已添加到单元测试中。

如果检查
RecurringJob
类的,您将看到其静态方法导致调用
RecurringJobManager
类:

public static class RecurringJob
{
    private static readonly Lazy<RecurringJobManager> Instance = new Lazy<RecurringJobManager>(
        () => new RecurringJobManager());

    //  ...

    public static void AddOrUpdate(
        Expression<Action> methodCall,
        string cronExpression,
        TimeZoneInfo timeZone = null,
        string queue = EnqueuedState.DefaultQueue)
    {
        var job = Job.FromExpression(methodCall);
        var id = GetRecurringJobId(job);

        Instance.Value.AddOrUpdate(id, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue);
    }

    //  ...
}
Job.FromExpression()
是一种可以安全使用的公共方法。但是,
GetRecurringJobId
是一个私有方法,定义如下:

private static string GetRecurringJobId(Job job)
{
    return $"{job.Type.ToGenericTypeString()}.{job.Method.Name}";
}
GetRecurringJobId
基本上以
SubmissionController的形式返回作业方法的名称。启动子任务
。它基于内部类
TypeExtensions
,带有
Type
的扩展方法。您不能直接使用这个类,因为它是内部的,所以您应该复制这个逻辑

如果您遵循此方法,您的最终解决方案将是:

类型扩展名(复制自):

嗯,这种方法会奏效,但我不喜欢。它是基于一些内部Hangfire的东西,将来可能会改变

这就是为什么我建议使用另一种方法。您可以添加新的facade接口(例如,
IRecurringJobFacade
),它将模拟您将要使用的
RecurringJob
中的方法。此接口的实现将只调用相应的
RecurringJob
方法。然后将这个
IRecurringJobFacade
注入控制器,并可以轻松地在UT中模拟它。以下是一个示例:

IRecurringJobFacade:

public static class RecurringJobManagerExtensions
{
    public static void AddOrUpdate(this IRecurringJobManager manager, Expression<Action> methodCall, Func<string> cronExpression, TimeZoneInfo timeZone = null, string queue = EnqueuedState.DefaultQueue)
    {
        var job = Job.FromExpression(methodCall);
        var id = $"{job.Type.ToGenericTypeString()}.{job.Method.Name}";

        manager.AddOrUpdate(id, job, cronExpression(), timeZone ?? TimeZoneInfo.Utc, queue);
    }
}
public class SubmissionController : Controller
{
    private readonly IRecurringJobManager recurringJobManager;

    public SubmissionController(IRecurringJobManager recurringJobManager)
    {
        this.recurringJobManager = recurringJobManager;
    }

    public IActionResult Post()
    {
        recurringJobManager.AddOrUpdate(() => InitiateSubmission(), Cron.Minutely);

        return Ok("Periodic submission triggered");
    }

    public void InitiateSubmission()
    {
        // ...
    }
}
public interface IRecurringJobFacade
{
    void AddOrUpdate(Expression<Action> methodCall, Func<string> cronExpression);

    //  Mimic other methods from RecurringJob that you are going to use.
    // ...
}
public class RecurringJobFacade : IRecurringJobFacade
{
    public void AddOrUpdate(Expression<Action> methodCall, Func<string> cronExpression)
    {
        RecurringJob.AddOrUpdate(methodCall, cronExpression);
    }
}
public class SubmissionController : Controller
{
    private readonly IRecurringJobFacade recurringJobFacade;

    public SubmissionController(IRecurringJobFacade recurringJobFacade)
    {
        this.recurringJobFacade = recurringJobFacade;
    }

    public IActionResult Post()
    {
        recurringJobFacade.AddOrUpdate(() => InitiateSubmission(), Cron.Minutely);

        return Ok("Periodic submission triggered");
    }

    public void InitiateSubmission()
    {
        // ...
    }
}
正如您所看到的,这种方法更简单,更重要的是,它更可靠,因为它不深入Hangfire内部,只像往常一样调用
RecurringJob
方法


当代码无法直接模拟时(静态方法或类不基于接口),通常使用这种facade接口。我在实践中使用的一些其他示例:模拟
System.IO.File
DateTime.Now
System.Timers.Timer
,等等。

您的回答帮助我将IRecurringJobFacade注入我的组件(一个服务,而不是控制器btw)和模拟重复作业功能。不过还有一个补充:在我的例子中,.AddOrUpdate方法的methodCall参数本身包含参数:recurringJobFacade.AddOrUpdate(activity.Id.ToString(),()=>RunTask(file,activity),cron);结果我得到了以下错误:“无法将lambda表达式转换为预期的委托类型,因为块中的某些返回类型不能隐式转换为委托返回类型”解决方案是在facade接口的实现中使用RecurringJobManager:公共类RecurringJobFacade:IRecurringJobFacade{public void AddOrUpdate(string recurringJobId,Expression methodCall,string cronExpression){RecurringJobManager=new RecurringJobManager();manager.AddOrUpdate(recurringJobId、methodCall、cronExpression);}}因此可以传递参数。对格式错误表示歉意。
public class RecurringJobFacade : IRecurringJobFacade
{
    public void AddOrUpdate(Expression<Action> methodCall, Func<string> cronExpression)
    {
        RecurringJob.AddOrUpdate(methodCall, cronExpression);
    }
}
public class SubmissionController : Controller
{
    private readonly IRecurringJobFacade recurringJobFacade;

    public SubmissionController(IRecurringJobFacade recurringJobFacade)
    {
        this.recurringJobFacade = recurringJobFacade;
    }

    public IActionResult Post()
    {
        recurringJobFacade.AddOrUpdate(() => InitiateSubmission(), Cron.Minutely);

        return Ok("Periodic submission triggered");
    }

    public void InitiateSubmission()
    {
        // ...
    }
}