Quartz.NET can invoke my class function if asp.net app pool is idle - asp.net

suppose i wrote & schedule Quartz.NET job related code in application_start event like this way
public class HelloJob : IJob
{
public void Execute(JobExecutionContext context)
{
//Send Mail
}
}
public static void ConfigureQuartzJobs()
{
// construct a scheduler factory
ISchedulerFactory schedFact = new StdSchedulerFactory();
// get a scheduler
IScheduler sched = schedFact.GetScheduler();
sched.Start();
// construct job info
JobDetail jobDetail = new JobDetail("myJob", null, typeof(HelloJob));
//created trigger which will fire every minute starting immediately
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("myTrigger", "group1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(60)
.RepeatForever())
.Build();
sched.ScheduleJob(jobDetail, trigger);
}
protected void Application_Start()
{
ConfigureQuartzJobs();
}
so i like to know that my routine will hit after every 60 second if app pool is idle or if no visitor is browsing my web site any page?
my main concern is i need to invoke a specific routine after 60 second if app pool is idle or active. or even no visitor is browsing my web site any page.
so please guide me what should i do? thanks

You can do following without Quartz:
1) In Application_End method check shutdown reason (you are interested in timeout).
http://msdn.microsoft.com/en-us/library/system.web.applicationshutdownreason(v=vs.110).aspx
2) When you get the idle timeout reason write and event with specific id to windows event log
3) Go to windows task scheduler. Create new task. Go to triggers. New trigger. in the trigger type combo box choose "On an event". Choose the log, source and event id you write to log. (by default Application)
4) Go to Actions->New->Send Email

Related

Changing property after specific time in ASP Core MVC

I'm working on a subscription-based system developed using Asp Core 3 MVC and Sql Server. The payment is handled externally, not linked to the application in any way. All I need to do in the application is to check the user's status, that is managed by an admin. When a user registers the status will be Pending, when the admin approves the user, Approval Date will be saved in the database, and the status will be changed to Approved.
The tricky thing for me is that I want the application to wait for 365 days before it changes the user status to Expired. I've no idea from where to start this part and would appreciate your help.
The simplest way i can think of without using hosted services is to add a check on user login that subtracts the approval date from today's date and check if the difference is equal or greater than 365 days
Something like this:
if ((DateTime.Now - user.ApprovalDate).TotalDays >= 365)
{
//Mark the user as expired...
}
You really shouldn't trigger a background thread from your main application code.
The correct way to do this is with a background worker process that has been designed specifically for this scenario.
ASP.NET Core 3 has a project type that is specifically for this, and will continue to run the back ground and can be used for all of your maintenance tasks. You can create a worker process using dotnet new worker -o YourProjectName or selecting Worker Service from the project selection window in Visual Studio.
Within that service you can then create a routine that will be used to determine if the user has expired. Encapsulate this logic in a class that makes testing easy.
Working repl has been posted here.
using System;
public class MainClass {
public static void Main (string[] args) {
var user = new User(){ ApprovedDate = DateTime.Today };
Console.WriteLine (UserHelper.IsUserExpired(user));
// this should be false
user = new User(){ ApprovedDate = DateTime.Today.AddDays(-180) };
Console.WriteLine (UserHelper.IsUserExpired(user));
// this should be false
user = new User(){ ApprovedDate = DateTime.Today.AddDays(-365) };
Console.WriteLine (UserHelper.IsUserExpired(user));
// this should be true
user = new User(){ ApprovedDate = DateTime.Today.AddDays(-366) };
Console.WriteLine (UserHelper.IsUserExpired(user));
}
}
public class User {
public DateTime ApprovedDate {get; set;}
}
public static class UserHelper
{
public static bool IsUserExpired(User user){
//... add all the repective logic in here that you need, for example;
return (DateTime.Today - user.ApprovedDate.Date).TotalDays > 365;
}
}

How to run an async task daily in a Kestrel process?

How do I run an async task in a Kestrel process with a very long time interval (say daily or perhaps even longer)? The task needs to run in the memory space of the web server process to update some global variables that slowly go out of date.
Bad answers:
Trying to use an OS scheduler is a poor plan.
Calling await from a controller is not acceptable. The task is slow.
The delay is too long for Task.Delay() (about 16 hours or so and Task.Delay will throw).
HangFire, etc. make no sense here. It's an in-memory job that doesn't care about anything in the database. Also, we can't call the database without a user context (from a logged-in user hitting some controller) anyway.
System.Threading.Timer. It's reentrant.
Bonus:
The task is idempotent. Old runs are completely irrelevant.
It doesn't matter if a particular page render misses the change; the next one will get it soon enough.
As this is a Kestrel server we're not really worried about stopping the background task. It'll stop when the server process goes down anyway.
The task should run once immediately on startup. This should make coordination easier.
Some people are missing this. The method is async. If it wasn't async the problem wouldn't be difficult.
I am going to add an answer to this, because this is the only logical way to accomplish such a thing in ASP.NET Core: an IHostedService implementation.
This is a non-reentrant timer background service that implements IHostedService.
public sealed class MyTimedBackgroundService : IHostedService
{
private const int TimerInterval = 5000; // change this to 24*60*60 to fire off every 24 hours
private Timer _t;
public async Task StartAsync(CancellationToken cancellationToken)
{
// Requirement: "fire" timer method immediatly.
await OnTimerFiredAsync();
// set up a timer to be non-reentrant, fire in 5 seconds
_t = new Timer(async _ => await OnTimerFiredAsync(),
null, TimerInterval, Timeout.Infinite);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_t?.Dispose();
return Task.CompletedTask;
}
private async Task OnTimerFiredAsync()
{
try
{
// do your work here
Debug.WriteLine($"{TimerInterval / 1000} second tick. Simulating heavy I/O bound work");
await Task.Delay(2000);
}
finally
{
// set timer to fire off again
_t?.Change(TimerInterval, Timeout.Infinite);
}
}
}
So, I know we discussed this in comments, but System.Threading.Timer callback method is considered a Event Handler. It is perfectly acceptable to use async void in this case since an exception escaping the method will be raised on a thread pool thread, just the same as if the method was synchronous. You probably should throw a catch in there anyway to log any exceptions.
You brought up timers not being safe at some interval boundary. I looked high and low for that information and could not find it. I have used timers on 24 hour intervals, 2 day intervals, 2 week intervals... I have never had them fail. I have a lot of them running in ASP.NET Core in production servers for years, too. We would have seen it happen by now.
OK, so you still don't trust System.Threading.Timer...
Let's say that, no... There is just no fricken way you are going to use a timer. OK, that's fine... Let's go another route. Let's move from IHostedService to BackgroundService (which is an implementation of IHostedService) and simply count down.
This will alleviate any fears of the timer boundary, and you don't have to worry about async void event handlers. This is also a non-reentrant for free.
public sealed class MyTimedBackgroundService : BackgroundService
{
private const long TimerIntervalSeconds = 5; // change this to 24*60 to fire off every 24 hours
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Requirement: "fire" timer method immediatly.
await OnTimerFiredAsync(stoppingToken);
var countdown = TimerIntervalSeconds;
while (!stoppingToken.IsCancellationRequested)
{
if (countdown-- <= 0)
{
try
{
await OnTimerFiredAsync(stoppingToken);
}
catch(Exception ex)
{
// TODO: log exception
}
finally
{
countdown = TimerIntervalSeconds;
}
}
await Task.Delay(1000, stoppingToken);
}
}
private async Task OnTimerFiredAsync(CancellationToken stoppingToken)
{
// do your work here
Debug.WriteLine($"{TimerIntervalSeconds} second tick. Simulating heavy I/O bound work");
await Task.Delay(2000);
}
}
A bonus side-effect is you can use long as your interval, allowing you more than 25 days for the event to fire as opposed to Timer which is capped at 25 days.
You would inject either of these as so:
services.AddHostedService<MyTimedBackgroundService>();

Use session variables in to Hangfire Recurring Job

I have integrated hangfire in to Asp.net web application and trying to use session variables in to Hangfire Recurring Job as like below :
public class Startup
{
public void Configuration(IAppBuilder app)
{
HangfireSyncServices objSync = new HangfireSyncServices();
var options = new DashboardOptions
{
Authorization = new[] { new CustomAuthorizationFilter() }
};
app.UseHangfireDashboard("/hangfire", options);
app.UseHangfireServer();
//Recurring Job
RecurringJob.AddOrUpdate("ADDRESS_SYNC", () => objSync.ADDRESS_SYNC(), Cron.MinuteInterval(30));
}
}
My “HangfireSyncServices” class as below:
public partial class HangfireSyncServices : APIPageClass
{
public void ADDRESS_SYNC()
{
string userName = Convert.ToString(Session[Constants.Sessions.LoggedInUser]).ToUpper();
//Exception throwing on above statement..
//........Rest code.......
}
}
public abstract class APIPageClass : System.Web.UI.Page
{
//common property & methods...
}
but I am getting run time exception as below at the time of getting value in to “userName”:
Session state can only be used when enableSessionState is set to true, either in a configuration file or in the Page directive. Please also make sure that System.Web.SessionStateModule or a custom session state module is included in the
section in the application configuration.
I have tried to resolve above error using this LINK & other solution also but not able to resolved yet. can anyone help me on this issue.
Thanks in advance,
Hiren
Hangfire jobs don't run in the same context as asp.net, it has it's own thread pool. In fact, Hangfire jobs may even execute on a different server than the one that queued the job if you have multiple servers in your hangfire pool.
Any data that you want to have access to from within the job needs to be passed in as a method parameter. For example:
public partial class HangfireSyncServices //: APIPageClass <- you can't do this..
{
public void ADDRESS_SYNC(string userName)
{
//........Rest code.......
}
}
string userName = Convert.ToString(Session[Constants.Sessions.LoggedInUser]).ToUpper();
RecurringJob.AddOrUpdate("ADDRESS_SYNC", () => objSync.ADDRESS_SYNC(userName), Cron.MinuteInterval(30));
Note that doing the above creates a recurring task that will always execute for the same user, the one that was triggered the web request that created the job.
Next problem: you're trying to create this job in the server startup, so there is no session yet. You only get a session when a web request is in progress. I can't help you with that because I don't have any idea what you're actually trying to do.

SignalR disconnects and does not reconnect

Whenever my application gets reset, signalR disconnects but does not reconnect.
I have a long running server task which sends updates to clients when each task is completed.
// inside action executed on every completion of a task
var h = new ForceHub();
h.MessageSent(email);
above code stops sending updates when application gets reset (i can emulate this problem by touching web.config).
I'd like a way to reconnect to a client. Currently the user has to reload the page for it to get updates again.
Here is my hub definition
public class ForceHub : Hub
{
public void MessageSent(string text)
{
GetContext().Clients.All.sent(text);
}
public void UpdateStatus(string msg)
{
GetContext().Clients.All.status(msg);
}
IHubContext GetContext()
{
return GlobalHost.ConnectionManager.GetHubContext<ForceHub>();
}
public override Task OnConnected()
{
try {
IoC.Resolve<ILogger>().Info("SignalR Connected -----------");
}catch (Exception){}
return base.OnConnected();
}
public override Task OnDisconnected()
{
try {
IoC.Resolve<ILogger>().Info("SignalR Disconnected -----------");
}
catch (Exception) { }
return base.OnDisconnected();
}
public override Task OnReconnected()
{
try {
IoC.Resolve<ILogger>().Info("SignalR Re-Connected -----------");
}
catch (Exception) { }
return base.OnReconnected();
}
}
I can see Connected and Re-Connected events triggered after startup, however after touching web.config, I don't see any of these events triggered.
i tried catching this on the client, but this event is not tirggered:
$.connection.hub.disconnected(function () {
console.error('signalR disconnected, retrying connection');
logError('Signal lost.');
setTimeout(function () { connection.start(); }, 1000);
});
update
I also hooked into State Changed event, which does get triggered, but the re-connection attempt below does not work.
$.connection.hub.stateChanged(function (state) {
console.debug('signalR state changed', state);
if (state.newState == 1) {
console.debug('restarting');
setTimeout(function () { $.connection.hub.start(); }, 1000);
}
});
this event gets triggered twice: newState is 2 ,and then 1.
I might have a clue... Touching the Web.config produces an appPool Recycle, meaning that a new worker process will be created for new requests while the existing process will continue for a while until the remaining requests end or the timeout is reached. Request that do not end in the timeout period are terminated.
Signalr client reconnects to the new process while the long running task is running in the old process, so when on the long running task you do
GlobalHost.ConnectionManager.GetHubContext<ForceHub>();
you actually get a reference for "old" hub while the client is connected to the "new" hub.
That's why the test preformed by Wasp worked: he was making a new request to publish on the signalr hub that was processed in the newly created worker process.
You could try to configure a singalr backplane (https://www.asp.net/signalr/overview/performance/scaleout-in-signalr), it’s really easy to configure it using Sql Server (https://www.asp.net/signalr/overview/performance/scaleout-with-sql-server). The backplane should be capable of connect the two worker processes and hopefully you will get the notification on the client.
If this is the problem, notifications generated by new requests will work even without the backplane. Notice that the real purpose of the backplane is to scale out signalr, this is, to connect a farm of WebServers between them.
Also keep in mind that running long-running task inside IIS is as task hard to achieve as, among other things, IIS does regular appPool recycles and has timeout limits for the requests to execute. I recommend that you read the following post: http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx
“If you think you can just write a background task yourself, it's likely you'll get it wrong. I'm not impugning your skills, I'm just saying it's subtle. Plus, why should you have to?”
Hope this helps

Quartz.Net embedded into Asp.NET MVC2, not firing off jobs

I'm trying to get Quartz.net working by embedding into my .Net MVC2 application. I know this is not ideal, but I'm just trying to get it up and running before moving it over to a service. I can't get my jobs to fire off, but I think I'm configured correctly. In my Global.asax.cs:
protected void Application_Start()
{
Quartz.IScheduler scheduler = BuildQuartzScheduler();
...
}
And the method, taken straight from the tutorial:
private IScheduler BuildQuartzScheduler()
{
// construct a scheduler factory
ISchedulerFactory schedFact = new StdSchedulerFactory();
// get a scheduler
IScheduler sched = schedFact.GetScheduler();
sched.Start();
// construct job info
JobDetail jobDetail = new JobDetail("myJob", null, typeof(QuartzController));
// fire every hour
Trigger trigger = TriggerUtils.MakeMinutelyTrigger();
// start on the next even hour
trigger.StartTimeUtc = TriggerUtils.GetEvenMinuteDate(DateTime.UtcNow);
trigger.Name = "myTrigger";
sched.ScheduleJob(jobDetail, trigger);
return sched;
}
And the "controller:"
public class QuartzController : IJob
{
public QuartzController() {
}
public void Execute(JobExecutionContext context) {
throw new NotImplementedException();
}
}
Nothing ever gets fired. What's going on? I'm sure there must be a simple syntax mistake, but it is driving me crazy!
If Application_Start looks like that, then I reckon your scheduler variable is likely to be garbage collected as soon as that method finishes executing.
I'd store a reference to the scheduler as a static variable in your HttpApplication class. This way, the reference hangs around for the duration of the process. A guess, but worth a shot.

Resources