Open tallesl opened 4 years ago
I have a few (small) programs that are structured using DI, broadly the approach is:
Register services during startup (as you would expect)
i.e. calls like appServices.AddTransient(serviceProvider => new Lazy
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();
Allow FluentScheduler to do its thing
Then inside the Execute method of the scheduled logic you can do something like:
var command = Program.ServiceProvider.GetService
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.
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.
I have a few (small) programs that are structured using DI, broadly the approach is:
- Register services during startup (as you would expect) i.e. calls like appServices.AddTransient(serviceProvider => new Lazy(serviceProvider.GetRequiredService));
- 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();
- Allow FluentScheduler to do its thing
- 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
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);
}
}
}
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
NonReentrantAsDefault();
@dev-greene Why set this, why not run parallel when it's a DI?
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.
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
Hi Joshua Yes, it works well for me. Not had any problems so far. I am using the latest Fluent Scheduler 5.5.1 with .Net 7. Cheers Gus
On Tue, 23 Jan 2024, 13:43 Rpgdudester, @.***> wrote:
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
(); 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
— Reply to this email directly, view it on GitHub https://github.com/fluentscheduler/FluentScheduler/issues/271#issuecomment-1906084115, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABHNLLOMMETEFFTHS76XEP3YP65BTAVCNFSM4MCXI6P2U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOJQGYYDQNBRGE2Q . You are receiving this because you commented.Message ID: @.***>
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.