Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependency injection help #271

Open
tallesl opened this issue Apr 7, 2020 · 10 comments
Open

Dependency injection help #271

tallesl opened this issue Apr 7, 2020 · 10 comments

Comments

@tallesl
Copy link
Contributor

tallesl commented Apr 7, 2020

There are some use cases of the library that we don't officially support, but we embrace any community effort on helping folks in need.

Using the library with dependency injection is one of those cases. If you have anything to say on this topic (bug report, question, suggestion, opinion, etc) please comment on this issue instead of opening a new one.

@tallesl
Copy link
Contributor Author

tallesl commented Apr 7, 2020

It is still in my todo list to offer a proper example in the docs, in the meanwhile these couple of comments may help giving an overall idea [1] [2].

@tallesl tallesl mentioned this issue Apr 7, 2020
@mrgfisher
Copy link

I have a few (small) programs that are structured using DI, broadly the approach is:

  1. Register services during startup (as you would expect)
    i.e. calls like appServices.AddTransient(serviceProvider => new Lazy(serviceProvider.GetRequiredService));

  2. Save the 'service provider' or equivalent in the program startup
    internal class Program
    {
    public static ServiceProvider ServiceProvider;
    ....
    var appServices = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
    ServiceProvider = appServices.BuildServiceProvider();

  3. Allow FluentScheduler to do its thing

  4. Then inside the Execute method of the scheduled logic you can do something like:
    var command = Program.ServiceProvider.GetService();
    command.Execute();
    Naturally implementations of IProcessBuildHourlyStatsCommand have lots of constructor requirements, but these are satisfied 'magically' :-)

To be clear, I only register the services used by the scheduled jobs, not the jobs themselves.

Hope this helps (anyone), am happy to provide more detailed code snippets if helpful.

@YZahringer
Copy link

YZahringer commented Apr 29, 2020

A simple solution, without specific DI framework dependency, would be to provide an IScheduleBuilder that can be registered as a singleton. Then this could be injected and used to create an ISchedule.

In ASP.NET Core, this allows the IScheduleBuilder to be injected into a IHostedService, which is responsible to create, maintain and starting/stoping the ISchedule.

Something like that:

public interface IScheduleBuilder 
{
    ISchedule Create(Action job, string cronExpression);
    ISchedule Create(Action job, Action<RunSpecifier> specifier));
}

public interface ISchedule
{
    bool Running { get; }
    DateTime? NextRun { get; }

    event EventHandler<JobStartedEventArgs> JobStarted;
    event EventHandler<JobEndedEventArgs> JobEnded;

    void UseUtc();
    void ResetScheduling();
    void SetScheduling(Action<RunSpecifier> specifier);
    void SetScheduling(string cron);
    void Start();
    void Stop();
    void StopAndBlock();
    void StopAndBlock(int timeout);
    void StopAndBlock(TimeSpan timeout);
}

public class MyHostedService : IHostingService
{
    private readonly IServiceScopeFactory _serviceScopeFactory;
    private readonly ISchedule _myScheduleTask;

    public MyHostedService(IServiceScopeFactory scopeFactory, IScheduleBuilder scheduleBuilder)
    {
        _myScheduleTask = scheduleBuilder.Create(MyTask, "* * * * *");
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _myScheduleTask.Start();
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _myScheduleTask.Stop();
    }

    private void MyTask()
    {
        using var scope = _serviceScopeFactory.CreateScope();
        var myJob = scope.ServiceProvider.GetRequiredService<IMyJob>();
        myJob.Execute();
    }
}

Another solution, coupled with ASP.NET Core DI and more plug-and-play, would be to support an AddSchedule<TJob>(Action<ScheduleOptions> options) where TJob : IScheduleJob extension method to IServiceCollection. An IHostedService would be registered automatically, it would create and configure the different ISchedule in background.

@danilobbezerra
Copy link

I have a few (small) programs that are structured using DI, broadly the approach is:

  1. Register services during startup (as you would expect)
    i.e. calls like appServices.AddTransient(serviceProvider => new Lazy(serviceProvider.GetRequiredService));
  2. Save the 'service provider' or equivalent in the program startup
    internal class Program
    {
    public static ServiceProvider ServiceProvider;
    ....
    var appServices = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
    ServiceProvider = appServices.BuildServiceProvider();
  3. Allow FluentScheduler to do its thing
  4. Then inside the Execute method of the scheduled logic you can do something like:
    var command = Program.ServiceProvider.GetService();
    command.Execute();
    Naturally implementations of IProcessBuildHourlyStatsCommand have lots of constructor requirements, but these are satisfied 'magically' :-)

To be clear, I only register the services used by the scheduled jobs, not the jobs themselves.

Hope this helps (anyone), am happy to provide more detailed code snippets if helpful.

Pls share your code with us

@dev-greene
Copy link

dev-greene commented Jul 16, 2021

I'm using IMediatr and FluentScheduler, and have developed this approach.

It's setup in Startup.cs simply:
JobManager.Initialize(new MyTaskRegistry (app));

And then uses a dispatch wrappper to properly queue mediatr commands.
In this MyMediatrCommand and MyLocalMediatrCommand are mediatr commands I've defined elsewhere

/// <summary>
/// Fluent Scheduler Task Registry that runs jobs on an automated schedule.
/// https://github.com/fluentscheduler/FluentScheduler
/// </summary>
public class MyTaskRegistry : Registry
{
    public MyTaskRegistry(IApplicationBuilder app)
    {
        // Get Dependencies
        var dispatcher = app.ApplicationServices.GetService<IMediator>();
        var environment = app.ApplicationServices.GetService<IWebHostEnvironment>();

        // Set so a second instance of a job won't be fired until the first has completed
        NonReentrantAsDefault();

        // Jobs for all non-local environments
        if (!environment.IsEnvironment("Local"))
        {
            Schedule(new DispatchJob<MyMediatrCommand, int>(dispatcher))
                .ToRunEvery(5).Minutes();
        }

        // Local Jobs for testing
        if (environment.IsEnvironment("Local"))
        {
            Schedule(new DispatchJob<MyLocalMediatrCommand, bool>(dispatcher, new MyLocalMediatrCommand()))
                .ToRunEvery(5).Seconds();
        }
    }

    /// <summary>
    /// Job wrapper that uses Mediator to dispatch a command. <br/>
    /// Ensures NonReEntrant policies are followed.
    /// </summary>
    private class DispatchJob<TRequest, TResponse> : IAsyncJob where TRequest : IRequest<TResponse>, new()
    {
        private readonly IMediator _dispatcher;
        private readonly IRequest<TResponse> _request;

        public DispatchJob(IMediator dispatcher, TRequest request)
        {
            _dispatcher = dispatcher;
            _request = request;
        }

        public DispatchJob(IMediator dispatcher)
        {
            _dispatcher = dispatcher;
            _request = new TRequest();
        }

        public async Task ExecuteAsync()
        {
            await _dispatcher.Send(_request);
        }

    }
}

@wapco
Copy link

wapco commented Mar 24, 2023

public async Task StartAsync(CancellationToken cancellationToken)
{
JobManager.Initialize(new SchedulerRegistry(_serviceProvider));
}

public class SchedulerRegistry : Registry
{
public SchedulerRegistry(IServiceProvider serviceProvider)
{
Schedule(() =>
{
using var scope = serviceProvider.CreateScope();
return scope.ServiceProvider.GetRequiredService();
}).ToRunNow().AndEvery(ClientSetting.HeartbeatInterval).Seconds();
}
}

@phoenixcoded20
Copy link

phoenixcoded20 commented Jun 1, 2023

NonReentrantAsDefault();

@dev-greene Why set this, why not run parallel when it's a DI?

@GusBeare
Copy link

GusBeare commented Jun 14, 2023

I am not sure if this is ideal/correct or not but I use IServiceScopeFactory. I've an app running .Net 7 currently but that began with .Net 5. It's been stable through all versions and works very well. I realised that I need to pass several services into my jobs such as DB repo and logging and this is the way I found to do it.

In Program.cs.

using FluentScheduler;
using Microsoft.Extensions.DependencyInjection;  // IServiceScopeFactory

// Fluent scheduler
IServiceScopeFactory serviceScopeFactory = app.Services.GetRequiredService<IServiceScopeFactory>();
JobManager.Initialize(new ScheduleRegistry(serviceScopeFactory));

Then Registry.cs.

using FluentScheduler;
using Microsoft.Extensions.DependencyInjection;

namespace MyCode.Code.Fluent_Scheduler;

public class ScheduleRegistry : Registry
{
    public ScheduleRegistry(IServiceScopeFactory serviceScopeFactory)
    {
        // check the bookings every minute for unpaid after 10 mins and set to abandoned
        Schedule(() => new BookingCleanupJob(serviceScopeFactory)).NonReentrant().ToRunNow().AndEvery(1).Minutes();

        // run the proc to clear out zombie rows once a day
        Schedule(() => new ZombieCleanupJob(serviceScopeFactory)).NonReentrant().ToRunEvery(1).Days();
    }
}

Then my job (in this case BookingCleanup) looks like this:

using System;
using System.Collections.Generic;
using System.Linq;

namespace MyCode.Code.Fluent_Scheduler;

public class BookingCleanupJob : IJob
{
    private IServiceScopeFactory serviceScopeFactory;

    public BookingCleanupJob(IServiceScopeFactory serviceScopeFactory)
    {
        this.serviceScopeFactory = serviceScopeFactory;
     }

    // executes the scheduled task
    public async void Execute()
    {
        using var serviceScope = serviceScopeFactory.CreateScope();
        ISqlDatabase _repository = serviceScope.ServiceProvider.GetService<ISqlDatabase>();
        IConfiguration _config = serviceScope.ServiceProvider.GetService<IConfiguration>();
        IWebHostEnvironment _env = serviceScope.ServiceProvider.GetService<IWebHostEnvironment>();
        ILogging _logging = serviceScope.ServiceProvider.GetService<ILogging>();

       try
       {
            const string sql = "; EXEC [dbo].[sp_SetAbandonedBookingsJob]";
            var AbandonedBookings = await _repository.GetListAsync<Booking>(sql) as List<Booking>;

           // logging example
            var desc = "sp_SetAbandonedBookingsJob was called and updated: " + AbandonedBookings.Count + " rows.";
            await _logging.LogAsync(Constants.LogType_BookingCleanupJobRUN, null, null, desc);

This has worked really well for me and the application has remained stable for several years now. I've tested upgrade to preview .Net 8 and it seemed fine.

@Rpgdudester
Copy link

I am not sure if this is ideal/correct or not but I use IServiceScopeFactory. I've an app running .Net 7 currently but that began with .Net 5. It's been stable through all versions and works very well. I realised that I need to pass several services into my jobs such as DB repo and logging and this is the way I found to do it.

In Program.cs.

using FluentScheduler;
using Microsoft.Extensions.DependencyInjection;  // IServiceScopeFactory

// Fluent scheduler
IServiceScopeFactory serviceScopeFactory = app.Services.GetRequiredService<IServiceScopeFactory>();
JobManager.Initialize(new ScheduleRegistry(serviceScopeFactory));

Then Registry.cs.

using FluentScheduler;
using Microsoft.Extensions.DependencyInjection;

namespace MyCode.Code.Fluent_Scheduler;

public class ScheduleRegistry : Registry
{
    public ScheduleRegistry(IServiceScopeFactory serviceScopeFactory)
    {
        // check the bookings every minute for unpaid after 10 mins and set to abandoned
        Schedule(() => new BookingCleanupJob(serviceScopeFactory)).NonReentrant().ToRunNow().AndEvery(1).Minutes();

        // run the proc to clear out zombie rows once a day
        Schedule(() => new ZombieCleanupJob(serviceScopeFactory)).NonReentrant().ToRunEvery(1).Days();
    }
}

Then my job (in this case BookingCleanup) looks like this:

using System;
using System.Collections.Generic;
using System.Linq;

namespace MyCode.Code.Fluent_Scheduler;

public class BookingCleanupJob : IJob
{
    private IServiceScopeFactory serviceScopeFactory;

    public BookingCleanupJob(IServiceScopeFactory serviceScopeFactory)
    {
        this.serviceScopeFactory = serviceScopeFactory;
     }

    // executes the scheduled task
    public async void Execute()
    {
        using var serviceScope = serviceScopeFactory.CreateScope();
        ISqlDatabase _repository = serviceScope.ServiceProvider.GetService<ISqlDatabase>();
        IConfiguration _config = serviceScope.ServiceProvider.GetService<IConfiguration>();
        IWebHostEnvironment _env = serviceScope.ServiceProvider.GetService<IWebHostEnvironment>();
        ILogging _logging = serviceScope.ServiceProvider.GetService<ILogging>();

       try
       {
            const string sql = "; EXEC [dbo].[sp_SetAbandonedBookingsJob]";
            var AbandonedBookings = await _repository.GetListAsync<Booking>(sql) as List<Booking>;

           // logging example
            var desc = "sp_SetAbandonedBookingsJob was called and updated: " + AbandonedBookings.Count + " rows.";
            await _logging.LogAsync(Constants.LogType_BookingCleanupJobRUN, null, null, desc);

This has worked really well for me and the application has remained stable for several years now. I've tested upgrade to preview .Net 8 and it seemed fine.

hey Gus, not sure if you're still around but I was wondering if you are still running this and how it works for you?

I'm currently in the process of moving stuff around in my c# app I wrote about 2 years ago, and I was wanting to work towards moving fluentscheduler towards a dependency injection system and if it's possible to actually do it using version 5 of fluentscheduler (since it doesn't look like it's active anymore to be able to use V6)

Thanks!

-Joshua

@GusBeare
Copy link

GusBeare commented Jan 23, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants