2

I am experimenting with FluentScheduler for some background tasks in ASP.net Core API.

The job should send push notifications every day at a particular time interval based on few criteria. I had gone through the document and implemented a test functionality to print some output in the console window. It worked as expected with predicted time interval.

But the actual job I am going to do with that involves database context which provides necessary information to perform the criteria to send out the notifications.

My problem is I am unable to use constructor with parameter in MyJob class which is throwing missing method exception

PS: As per this article from Scott Hanselman, FluentScheduler seems to be quite famous but I could not get any help from online communities. But obviously, it's quite easy to grasp.

public class MyJob : IJob
{
    private ApplicationDbContext _context;

    public MyJob(ApplicationDbContext context)
    {
        _context = context;
    }

    public void Execute()
    {
        Console.WriteLine("Executed");
        SendNotificationAsync();
    }

    private async Task SendNotificationAsync()
    {
        var overdues = _context.Borrow.Join(
            _context.ApplicationUser,
            b => b.ApplicationUserId,
            a => a.Id,
            (a, b) => new { a, b })
            .Where(z => (z.a.ReturnedDate == null) && (z.a.BorrowApproval == 1))
            .Where(z => z.a.ReturnDate.Date == new DateTime().Date.AddDays(1).Date)
            .Select(z => new { z.a.ApplicationUserId, z.a.Book.ShortTitle, z.a.BorrowedDate, z.b.Token })
            .ToList();

        Console.WriteLine("Acknowledged");

        foreach (var r in overdues)
        {
            string message = "You are running late! The book '" + r.ShortTitle + "' borrowed on '" + r.BorrowedDate + "' due tomorrow.";
            Console.WriteLine(message);
            await new PushNotificationService().sendAsync(r.Token, "Due Tomorrow!", message);
        }
    }
}
Henk Mollema
  • 44,194
  • 12
  • 93
  • 104
Kirk
  • 4,957
  • 2
  • 32
  • 59

3 Answers3

2

From the source code for IJob, it looks like your class that implements IJob needs to have a parameterless default constructor. Since FluentScheduler also supports lambdas, it may be easier to have your dependency injection library create your object, then call the Execute method like so:

var myJob = new MyJob(new ApplicationDbContext());
Schedule(() => myJob.Execute()).ToRunEvery(1).Days().At(21, 15);

Or call the constructor yourself:

// Schedule a job using a factory method and pass parameters to the constructor.
Schedule(() => new MyJob(new ApplicationDbContext())).ToRunNow().AndEvery(2).Seconds();
Jonathan Tyson
  • 463
  • 4
  • 9
  • new ApplicationDbContext() Isn't supposed to be instantiated object already from ConfigureServices? – Kirk Aug 09 '17 at 15:20
  • It's just a placeholder for however you are obtaining `ApplicationDbContext` – Jonathan Tyson Aug 09 '17 at 15:29
  • new MyJob(new ApplicationDbContext()) isn't accepting. same error as no mssing method error – Kirk Aug 09 '17 at 15:47
  • How are you obtaining an instance of `ApplicationDbContext` currently? – Jonathan Tyson Aug 09 '17 at 16:21
  • I am not able to. That is the problem. Somehow I can inject the already running instance of the DbContext into the Schedule I will be able to accomplish my task. – Kirk Aug 10 '17 at 19:54
  • Oh, can you add a parameterless default constructor for `MyJob` and inject the `ApplicationDbContext` into a property on `MyJob` instead? – Jonathan Tyson Aug 11 '17 at 15:04
  • If I need to do such way, I need to do in Main function. but the new ApplicationDbContext() requires connection string parameters here. then there will be two instances running. right? – Kirk Aug 11 '17 at 15:13
  • If you don't provide an `Action` function or `Func` like in the second example, Fluent Scheduler will call `Activator.CreateInstance()` to try to create an instance of `MyJob`, which expects `MyJob` to have a parameterless constructor. There isn't a way around this, the dependency injection framework you are using should be able to create an instance of `MyJob` with `ApplicationDbContext`, which can then be scheduled in either of the two ways above. [See here](https://stackoverflow.com/q/35696411/5951133) for some additional info – Jonathan Tyson Aug 11 '17 at 18:41
2

Note - In the documentation it says not to use IJobFactory because it is going to be deprecated soon, but it is working for me in Production for past 3,4 months - https://github.com/fluentscheduler/FluentScheduler/issues/71

I am using Autofac and Fluent Scheduler in a Windows Console Application which uses TopShelf to run it as a Windows Service.

To use Dependency Injection in FluentScheduler you have to setup a Job Factory to resolve the dependencies.

So first setup a JobFactory by implementing IJobFactory

Like this -

public class MyJobFactory : IJobFactory
{
    public IJob GetJobInstance<T>() where T : IJob
    {
        return MyContainer.GetJobInstance<T>();
    }
}

Now in the Job Factory because I'm using Autofac I am setting up a Container which has 2 methods

1 - ConfigureDependencies() - Which is only called once to setup the Container:

2 - GetJobInstance() - Which is called by the MyJobFactory to resolve t a Job instance

public class MyContainer
{
    public static IContainer Container { get; set; }

    public static void ConfigureDependencies()
    {
        var builder = new ContainerBuilder();

        // Jobs
        builder.RegisterType<MyJob>().As<MyJob>();

        // DB Contexts

        // Others

        Container = builder.Build();
    }


    public static IJob GetJobInstance<T>() where T : IJob
    {
        return Container.Resolve<T>();
    }
}

Then when you Start you application/service, it will look something like this.

public void Start()
{
    // Configure Dependency Injection
    MyContainer.ConfigureDependencies();

    // Setup the Fluent Scheduler - 
    JobManager.JobFactory = new MyJobFactory();

    JobManager.UseUtcTime();

    JobManager.Initialize(new MyJobRegistry());     
}

Then I've created a Job by adding 2 constructors -

1 - Parameter-less

2 - The constructor with Injected Objects

e.g.

public class MyJob : IJob
{
    public static string JobName = "MyJob";

    private readonly ICompaniesProvider _companiesProvider;

    // parameter-less constructor
    public MyJob() { }

    // injecting HERE
    public MyJob(ICompaniesProvider companiesProvider)
    {
        _companiesProvider = companiesProvider;

    }

    public void Execute()
    {



    }
}

EDIT

Because of deprecated issue of IJobFactory, you could just call the Container Directly and get a Job Instance before adding it to the JobManager

public void Start()
{
    // Configure Dependency Injection
    MyContainer.ConfigureDependencies();

    JobManager.UseUtcTime();
    JobManager.Start();

    var myJob = MyContainer.GetJobInstance<MyJob>();

    Action<Schedule> schedule = s => s.ToRunEvery(1).Hours().At(0);

    JobManager.AddJob(myJob, schedule);
}
Dawood Awan
  • 7,051
  • 10
  • 56
  • 119
0

I have posted the similar answer here- https://stackoverflow.com/a/51167073/7166609

I have used the Application service scope, injected in the registry's constructor and used this scope to get the objects in the execute method of my job class.

Gaurav Jalan
  • 467
  • 2
  • 14