Quartz.Net ASP.NET Status update - asp.net

I'm using quartz in an asp.net site and I'm using it to fire off a longer background task. I want to be able to use a timer or jquery to call the job and get back status and progress info similar to how I was doing it before. Here is code that I was using:
protected void Timer1_Tick(object sender, EventArgs e)
{
if (MyTaskManager.Instance.IsTaskRunning)
{
if (MyTaskManager.Instance.TotalItems > 0) ProgressLabel.Text = string.Format(ProgressLabel.Text, MyTaskManager.Instance.TotalItems, MyTaskManager.Instance.ItemsProcessed);
else ProgressLabel.Text = string.Format("Records Processed: {0}", MyTaskManager.Instance.ItemsProcessed);
}
else
{
Timer1.Enabled = false;
}
}
Has anyone done this that could point me in the right direction?

It might not be what you're looking for but I've used SignalR with Quartz.Net and it works great.
I've published a simple application in my repository.
You have to create a custom Hub which you will use to interact with your ASP.NET page.
Your (quartz.net) job will interact your ASP.NET page through your Hub the same way.
Once you have installed ASP.NET SignalR:
Install-Package Microsoft.AspNet.SignalR
You can create a startup class to configure SignalR:
[assembly: OwinStartup(typeof(AspNetQuartSignalR.Startup))]
namespace AspNetQuartSignalR
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}
You have to reference a couple of scripts in your page:
jquery-2.1.1.js
jquery.signalR-2.1.1.js
and the automatically generated /signalr/hubs
Now you can create your own Hub:
public class QuartzHub : Hub
{
...
}
with methods which will allow you to interact with the scripts in your ASP.NET page.
Let's say your Hub has a method CheckQuartzStatus which gives you the status of all the quartz.net triggers configured:
public void CheckQuartzStatus()
{
string message = string.Empty;
var allTriggerKeys = Global.Scheduler.GetTriggerKeys(GroupMatcher<TriggerKey>.AnyGroup());
foreach (var triggerKey in allTriggerKeys)
{
ITrigger trigger = Global.Scheduler.GetTrigger(triggerKey);
message += string.Format("{0} = {1}", trigger.Key, Global.Scheduler.GetTriggerState(trigger.Key)) + Environment.NewLine;
}
Clients.All.onCheckQuartzStatus(message);
}
Your jQuery script can interact with this method in a very simple way:
quartz.server.checkQuartzStatus();
As you can see your Hub method at some point fires an action: onCheckQuartzStatus.
That is a call to an event defined in your javascript defined in the page :
quartz.client.onCheckQuartzStatus = function (message) {
alert(message);
};
You can see how the interaction works looking at the script in the Default.aspx page.
You can read a lot more here.

You're going to have to build all of this functionality yourself. Quartz.Net jobs run in the threadpool and you don't have a way of referencing them from the scheduler. You could do this by having the job write its progress somewhere and then have your timer check that spot for progress.

Related

ASP.NET Some service register after mediator

We are working on service which collect data from AWS SQS then send batch to client. We are using mediator to publish notifications. The diagram of program looks like:
The problem is in first NotificationHandler from Mediatr.
private readonly EventCollectorHostedService _collector;
public CollectIncomingEventNotificationHandler(EventCollectorHostedService collector)
{
_collector = collector;
}
Class EventCollectorHostedService is register after Mediator so is not visible during registering this NotificationHandler and additionally it use Mediator to publish notification that batch is ready to send.
The error is that cannot construct CollectIncomingEventNotificationHandler because -> Unable to resolve service for type 'Api.Services.HostedServices.EventCollectorHostedService'.
services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly);
services.AddHostedService<EventCollectorHostedService>();
The ugly solution is to declare some functionality in EventCollectorHostedService as static or instead of injecting EventCollectorHostedService, inject IServiceProvider.
But these solution don't look clean for me so do you have any other better solution ?
Thanks in advance.
Maybe someone encountered with similar problem so finally i have a brilliant solution.
Background services have to be treat like separate microservices based on event driven architecture so we have to make internal message broker mechanism.
The very simple solution which cover my case is:
public class NotificationChannel : INotificationChannel
{
public event EventHandler<IncomingEventNotificataionEventArgs> IncomingEventReceived;
public void Publish<T>(T notification)
{
if(notification is IncomingEventNotification incomingEventNotification)
{
OnIncomingEventReceived(incomingEventNotification);
}
}
protected virtual void OnIncomingEventReceived(IncomingEventNotification notification)
{
if(IncomingEventReceived != null)
{
var args = new IncomingEventNotificataionEventArgs(notification);
IncomingEventReceived(this, args);
}
}
}

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 - access clients from server-side business logic

I have a requirement to start a process on the server that may run for several minutes, so I was thinking of exposing the following hub method:-
public async Task Start()
{
await Task.Run(() => _myService.Start());
}
There would also be a Stop() method that allows a client to stop the running process, probably via a cancellation token. I've also omitted code that prevents it from being started if already running, error handling, etc.
Additionally, the long-running process will be collecting data which it needs to periodically broadcast back to the client(s), so I was wondering about using an event - something like this:-
public async Task Start()
{
_myService.AfterDataCollected += AfterDataCollectedHandler;
await Task.Run(() => _myService.Start());
_myService.AfterDataCollected -= AfterDataCollectedHandler;
}
private void AfterDataCollectedHandler(object sender, MyDataEventArgs e)
{
Clients.All.SendData(e.Data);
}
Is this an acceptable solution or is there a "better" way?
You don't need to use SignalR to start the work, you can use the applications already existing framework / design / API for this and only use SignalR for the pub sub part.
I did this for my current customers project, a user starts a work and all tabs belonging to that user is updated using signalr, I used a out sun library called SignalR.EventAggregatorProxy to abstract the domain from SignalR. Disclaimer : I'm the author of said library
http://andersmalmgren.com/2014/05/27/client-server-event-aggregation-with-signalr/
edit: Using the .NET client your code would look something like this
public class MyViewModel : IHandle<WorkProgress>
{
public MyViewModel(IEventAggregator eventAggregator)
{
eventAggregator.Subscribe(this);
}
public void Handle(WorkProgress message)
{
//Act on work progress
}
}

How can i create a webservice which is checking table for some periodically in database in asp.net?

I wanted to checking my database table for periodically.So how can i create a webservice and how can i configure it.
basically what you need is, something which is always running and hence can make periodic calls.
There are a number of ways to do it
(Since ASP.NET hence) You can make a Windows Service, and host this service on your server, since server is always running, this Windows Service will make request to your webservice, update database or watever you want
You can use SQL Jobs to do it. You can call a webservice from a job, through a SSIS (Sql Server Integration Service) Package. These packages are very very robust in nature, they can do almost any db activity that you want them to do, including webservice request.
And finally, you can use third party tools such as Quartz.Net
References:
this is how you can call a webservice through a windows service.
this is how you can call a webservice through a ssis package.
this is how you can integrate a SSIS package in a SQL Job
this is how you can create a windows service
this is how you can create a SSIS package
this is how you can get answer/tutorial of almost anything
Example:
simplest of all of these would be a Windows Service. Making a windows service and hosting it on the machine(server) is very easy, use one of the given links (specially the last link). Usually, in Windows Service, you do some activity in OnStart event. you can place a timer inside this OnStart and upon TimerTick(), you can request your webservice.
something like this:
class Program : ServiceBase
{
System.Timers.Timer timer;
static void Main(string[] args)
{
ServiceBase.Run(new Program());
}
public Program()
{
this.ServiceName = "My Service";
}
protected override void OnStart(string[] args)
{
base.OnStart(args);
InitializeTimer();
}
protected override void OnStop()
{
base.OnStop();
//TODO: clean up any variables and stop any threads
}
protected void InitializeTimer()
{
try
{
if (timer == null)
{
timer = new System.Timers.Timer();
timer.Enabled = true;
timer.AutoReset = true;
timer.Interval = 60000 * 1;
timer.Enabled = true;
timer.Elapsed += timer_Elapsed;
}
}
catch (Exception ex)
{
Utility.WriteLog("Exception InitialiseTimer : " + ex.Message.ToString());
}
finally
{
}
}
protected void timer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
{
TimerTick();
timer.Interval = 60000 * Convert.ToDouble(ConfigurationManager.AppSettings["TimerInerval"]);
}
private void TimerTick()
{
try
{
DownloadFromFTPandValidate objDownLoadandValidate = new DownloadFromFTPandValidate();
objDownLoadandValidate.ProcessMain();
}
catch (Exception ex)
{
Utility.WriteLog("Exception InitialiseTimer : " + ex.Message.ToString());
}
}
}
Here, class DownloadFromFTPandValidate wraps the code to db activity. It shd give you an idea.
You will need a job scheduler for periodical task. I recommend you a good one. Check out this link: http://quartznet.sourceforge.net/
Why not using a trigger on your table which runs a stored procedure once data was modified, then use the xp_cmdshell to access the commandline form your stored procedure so you can run for example a batch file or whatever.

How can I utilize or mimic Application OnStart in an HttpModule?

We are trying to remove the global.asax from our many web applications in favor of HttpModules that are in a common code base. This works really well for many application events such as BeginRequest and PostAuthentication, but there is no Application Start event exposed in the HttpModule.
I can think of a couple of smelly ways to overcome this deficit. For example, I can probably do this:
protected virtual void BeginRequest(object sender, EventArgs e)
{
Log.Debug("Entered BeginRequest...");
var app = HttpContext.Current.Application;
var hasBeenSet app["HasBeenExecuted"] == null ? false : true;
if(!hasBeenSet)
{
app.Lock();
// ... do app level code
app.Add("HasBeenExecuted", true);
app.Unlock();
}
// do regular begin request stuff ...
}
But this just doesn't smell well to me.
What is the best way to invoke some application begin logic without having a global.asax?
Just keep a static bool in the HttpModule:
private static bool _hasApplicationStarted = false;
private static object _locker = new object();
private void EnsureStarted()
{
if (_hasApplicationStarted) return;
lock (_locker)
{
if (_hasApplicationStarted) return;
// perform application startup here
_hasApplicationStarted = true;
}
}
Then have any method that needs the application to have started just call EnsureStarted.
HttpModules and HttpHandlers will execute on every single request, while the Global.asax App Start event is when the application starts, thus only once.
You could make a general global.asax which will load all assemblies with a specific interface, and then drop in the dll's you want executed for that specific application. Or even register them in your web.config, and have your general global.asax read the keys, and then load and execute the code you want.
I think this is better than putting app once code in a module and checking on a state variable.

Resources