Quartz scheduler. Schedule job during asp.net application start - c#

I want to use task scheduler to create thread during application start.
I made it thanks to this and this, but something goes wrong and job is not running, of course is initialized before.
My class which is run before start:
[assembly: WebActivatorEx.PreApplicationStartMethod(
typeof(Application.App_Start.TaskScheduler), "Start")]
namespace Application.App_Start
{
public static class TaskScheduler
{
private static readonly IScheduler scheduler = new StdSchedulerFactory().GetScheduler();
private static void CreateTaskToDeleteTmpFiles(Object sender)
{
scheduler.Start();
//Create job which will be add to thread
IJobDetail job = JobBuilder.Create<DeleteTmpJob>()
.WithIdentity("ClearTmpFiles")
.StoreDurably()
.Build();
//Create thread which run the job after specified conditions
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("ClearTmpFiles")
.StartAt(DateBuilder.FutureDate(1, IntervalUnit.Second))
.Build();
//Add Job and Trigger to scheduler
scheduler.ScheduleJob(job, trigger);
}
}
}
My job class:
public class DeleteTmpJob : IJob
{
private IDocumentStore documentStore;
private IUploaderCollection uploaderCollection;
public DeleteTmpJob(IDocumentStore _documentStore, IUploaderCollection _uploaderCollection)
{
documentStore = _documentStore;
uploaderCollection = _uploaderCollection;
}
public void Execute(IJobExecutionContext context)
{
documentStore.ClearTmpDirectory();
}
}
Job is not running
Anyone can help?

Have you tried using an empty constructor for your job?
"Each (and every) time the scheduler executes the job, it creates a new instance of the class before calling its Execute(..) method. One of the ramifications of this behavior is the fact that jobs must have a no-arguement constructor."
You may need to implement your own JobFactory to allow you to use DI. How you implement it depends on which library you are using.
"When a trigger fires, the JobDetail (instance definition) it is associated to is loaded, and the job class it refers to is instantiated via the JobFactory configured on the Scheduler.The default JobFactory simply calls the default constructor of the job class using Activator.CreateInstance, then attempts to call setter properties on the class that match the names of keys within the JobDataMap. You may want to create your own implementation of JobFactory to accomplish things such as having your application's IoC or DI container produce/initialize the job instance."
source: see here

I had the same problem, when I deleted constructor job worked. First try to call base constructor, if it is still not working try to delete constructor.

Related

Quartz .net call scheduler outside of DI container or Main method

We are trying to use quartz for some of our scheduled tasks such as email and record duplication. It seems to be a perfect fit however, the documentation hasn't really been updated for DI stuff and repo unit of work patterns so we have an issue. We have a separate project in our solution where we want to house our scheduler and different jobs, which will have access to our service layer where each Job calls our service. The issue is, we aren't sure how to get access to the scheduler after it gets instantiated in the job runner project's main file.
Main file:
private static async Task Main(string[] args)
{
LogProvider.SetCurrentLogProvider(new ConsoleLogProvider());
// Grab the Scheduler instance from the Factory
StdSchedulerFactory factory = new StdSchedulerFactory();
IScheduler scheduler = await factory.GetScheduler();
// and start it off
await scheduler.Start();
// and last shut down the scheduler when you are ready to close your program
await scheduler.Shutdown();
}
Typical Job:
using Quartz;
using Compyl.AppLogic.AssessmentService;
namespace JobRunner.Jobs
{
public class AssessmentDuplicationJob : IJob
{
private readonly IAssessmentService _assessmentService;
public AssessmentDuplicationJob(IAssessmentService assessmentService)
{
_assessmentService = assessmentService;
}
public async Task Execute(IJobExecutionContext context)
{
var dataMap = context.MergedJobDataMap;
var id = dataMap.GetInt("id");
var shouldPopulateAnswers = dataMap.GetBoolean("shouldPopulateAnswers");
_assessmentService.DuplicateAssessment(id, shouldPopulateAnswers);
}
}
}
we want to have a class or method in the job runner project that houses the scheduler through DI and a method(s) like
[Inject] IScheduler scheduler
public ScheduleEntity (T entity)
{
var job = createJob(entity)//another method that creates the job and jobdatamap this is easy enough
var trigger = createTrigger(entity)// same as above
scheduler.ScheduleJob(job, trigger)
}
Then we would call the ScheduleEntity method wherever we needed it in our webapp.
Not sure if this is the "correct" way, but what we're doing is setting up the default scheduler in the DI container and then using it in each component we need it by calling the factory's get scheduler method, and then using a static class from our other project to create and schedule jobs, using the scheduler as a parameter.

How to use dependency injection for transient instances in async tasks loop

There is a class that implements an interface called SomethingManager.cs
like:
public class SomethingManager : ISomethingManager
This is a worker service in .net 6 and there is another class library project in the same solution that contains the interface and implementation of SomethingManager.
Dependencies are being registered in the worker service project like
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureServices((hostContext, services) =>
{
//AddSingleton or Transient here?
services.AddSingleton<ISomethingManager, SomethingManager>();
...
The problem is that, in the entrypoint project that works in an async way,there is a loop like:
foreach (xml in xmls)
{
tasks.Add(StartProcessAsync(xml));
}
await Task.WhenAll(tasks);
Inside StartProcessAsync it uses SomethingManager instance hat was previously registered and injected in the constructor.
The problem is that the class SomethingManager has some private members that are supposed to be unique for every task and I noticed that in this way it causes fatal errors between the tasks.Actualy this class needs to share a sessionId that a method .Connect() is giving the value every time.We have to call .Connect() method, one time before other actions,inside every task.
So,
My question is how can I register the SomethingManager with Dependency Injection and every task that uses this instance (which is registered with DI) to have different values for its private members?
And, if can't do it in this way, am I supposed to create new instance for this every time?
public Task StartProcessAsync(xmlFileInfo xml)
{
return Task.Run(async () =>
{
//this one doesn't work inside tasks loop it cases problems because
//the sessionId that contains has to be different for every task
//_somethingManager.DoSomething();
//Like this?
var somethingManager= SomethingManager(_someSettings);
somethingManager.DoSomething();
var mem = somethingManager.ThePrivateMember;
//another object which has also private members in the same class.
});
}

Hangfire problem when running two methods inside the Enqueue

I have an extension to enqueue my view models pointing to an implementation of an interface IBackgroundJob
this are my extensions methods
private static readonly ActivitySource activitySource = new("MC.Hangfire.Extensions");
public static string Enqueue<T>(this T job, IBackgroundJobClient client)
{
return client.Enqueue<IBackgroundJob<T>>(ps => ps.AddTelemetry(null).EnqueueJob(null, job, JobCancellationToken.Null));
}
public static IBackgroundJob<T> AddTelemetry<T>(this IBackgroundJob<T> job, PerformContext context)
{
using var activity = activitySource.StartActivity($"Start Job {typeof(T).FullName} id {context.BackgroundJob.Id}", ActivityKind.Server);
activity?.SetTag("JobId", context.BackgroundJob.Id);
activity?.SetTag("JobJson", Newtonsoft.Json.JsonConvert.SerializeObject(job));
activity?.SetTag("Job", Newtonsoft.Json.JsonConvert.SerializeObject(context.BackgroundJob.Job));
return job;
}
My problem is that the EnqueueJob method is called, but the AddTelemetry method is not called before, how can I Add the telemetry information before calling all of my jobs, but in the context of the jobs, and of course not adding this code in all of my enqueue methods?
I'm looking for the Hangfire filters, but I think that there should be a way to inject the filter with the DI of the ASP.NET core application
I created this issue on github because I think that the problem with instrumentation is a little deeper in the code
https://github.com/HangfireIO/Hangfire/issues/2017

How to Set up QuartzNet to Execute Recurring Background Job?

In an MVC5 web application using Net 4.8, I am trying to do the following:
When launching the application, I would like a scheduler to trigger a job every 2 Minutes.
The job reads and processes messages from a message queue.
I previously used Hangfire for this and it worked quite well.
However, I was told not to use Hangfire for this application. As an alternative, I opted for Quartz.NET here but I am currently having trouble setting up and triggering
the desired action. Following the Quartz.NET documentation, this is my current setup.
public class DummyJob :IJob
{
private readonly ISomeInterface _someInterface;
public DummyJob(ISomeInterface someInterface)
{
_someInterface = someInterface;
}
public async Task Execute(IJobExecutionContext context)
{
await _someInterface.ProcessMessages();
}
}
Next, the job configuration
public class JobScheduler
{
public static void Start()
{
// Run every 2 minutes
const string cron = #"0 0/2 * * * ?";
ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
var scheduler = (IScheduler)schedulerFactory.GetScheduler().Result;
scheduler.Start();
var job = JobBuilder.Create<DummyJob>().Build();
var trigger = TriggerBuilder
.Create()
.WithCronSchedule(cron)
.Build();
scheduler.ScheduleJob(job, trigger);
}
}
I tried starting the job from both Startup.cs and Global.asax. Although the job was created, it did not fire when launching the application.
public void Configuration(IAppBuilder app)
{
[...]
JobScheduler.JobScheduler.Start();
}
What am I missing in terms of setting up QuartzNet? Do I need to put JobScheduler.JobScheduler.Start(); into Global.asax.cs or Startup.cs?

How to implement scheduled task on EF (DB first) entities?

I am quite new to Asp.net and have a website using Entity Framework. Every night, I need to do some work on my Person entities.
Thus I installed Quartz.Net et tried to use it this way in Global.asax :
<%# Application Language="C#" %>
<%# Import Namespace="Quartz" %>
<%# Import Namespace="Quartz.Impl" %>
<script runat="server">
private IScheduler Scheduler { get; set; }
void Application_Start(object sender, EventArgs e)
{
Scheduler = StdSchedulerFactory.GetDefaultScheduler();
Scheduler.Start();
IJobDetail dailyReset = JobBuilder.Create<ApplicationJobs.DailyReset>()
.WithIdentity("dailyReset", "group1")
.Build();
ITrigger dailyResetTrigger = TriggerBuilder.Create()
.WithIdentity("dailyResetTrigger", "group1")
.StartAt(DateBuilder.DateOf(3, 0, 0))
.WithSimpleSchedule(x => x
.WithIntervalInHours(24)
.RepeatForever())
.Build()
Scheduler.ScheduleJob(dailyReset, dailyResetTrigger);
}
</script>
Then my ApplicationJobs class :
public class ApplicationJobs : System.Web.HttpApplication
{
public class DailyReset : IJob
{
public void Execute(IJobExecutionContext context)
{
using (var uow = new UnitOfWork())
{
foreach (Person person in uof.Context.Persons)
{
//do something
}
}
}
}
}
And finally the UnitOfWork :
public class UnitOfWork : IDisposable
{
private const string _httpContextKey = "_unitOfWork";
private MyEntities _dbContext;
public static UnitOfWork Current
{
get { return (UnitOfWork)HttpContext.Current.Items[_httpContextKey]; }
}
public UnitOfWork()
{
HttpContext.Current.Items[_httpContextKey] = this;
}
public MyEntities Context
{
get
{
if (_dbContext == null)
_dbContext = new MyEntities();
return _dbContext;
}
}
}
But using (var uow = new UnitOfWork()) is not working because of HttpContext.Current.Items[_httpContextKey] = this; in uow's constructor ; I read that HttpContext.Current was not available in Application_Start.
In read related posts, notably this one but I don't really understand if I do need to create something like UnitOfWorkScope described here, or if there could be a way to do that as it currently is.
Then is there any clean and safe way to schedule some task which would use my UnitOfWork in order to update entities ?
Thanks a lot.
Your problem come from the fact that when your job will run, it wil be called by the quartz scheduller, not from an http request (even if the job is in an ASP website).
So HttpContext.Current will be most likely null.
Keep in mind when using Quartz that you shoudl see it as a totally paralle process to your website, almost like a separate service.
If you need to pass "argument" to your job, you can use the job data map
JobDataMap dataMap = jobContext.JobDetail.JobDataMap;
(see here for more info : http://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/more-about-jobs.html)
If you need to access your job, just use the same key and group when creating a jobkey (the one you used in WithIdentity
Note that it is recommended for entity context to be alive only for the time of the action you need it, so you could probably just instantiate a new context at the start of the job and dispose it at the end.
The issue is that you're not executing the job within a web request. As in, web request starts, you check outstanding work, do work if required, request ends. Without a web request you have no context - as the context is for the lifetime of the web request and accessible via the request thread.
Another issue you're going to have is app-pool, using default settings, may end if there's no activity. So you would need a way to keep it alive.
An alternative method is to use something like win task scheduler to hit the website to kick off the work.

Categories