Include scheduling in web-application in asp.net - asp.net

I wanted to run scheduling process in asp.net periodically in web application.
In brief,My database table is having date & deadline Hrs.I want to calculate expected dateTime from both then another table get updated (inserts 1000s of records) periodically & also want to run process of mail sending according to that calculation for the same.
This is expected scheduled process which should be executed periodically.

The Quartz.NET job scheduler library is excellent for this sort of thing.

You can use Window Service to work in backgroud or scheduling , please see below links:
Using Timers in a Windows Service

Here's what I did:
public class Email {
private static System.Threading.Timer threadingTimer;
public static void StartTimer()
{
if (threadingTimer == null)
threadingTimer = new Timer(new TimerCallback(Callback), HttpContext.Current, 0, 20000);
}
private static void Callback(object sender)
{
if (/* Your condition to send emails */)
using (var context = new MyEntities())
{
var users = from user in context.Usere
select user;
foreach (var user in users)
{
// Send an email to user
}
}
}
}
And you have to add this to Application_Start:
void Application_Start(object sender, EventArgs e)
{
EMail.StartTimer();
}

Check out this old article from Jeff Atwood:
Easy Background Tasks in ASP.NET
Basically he suggests that you use the cache expiration mechanism to schedule a timed task. The problem is: your web application needs to be running. What if the website isn't called at all? Well, since IIS 7.5 there is the possibility to keep your web app running at all times: auto starting web apps. Jeff suggests in the comments that his approach served well until they outgrew it. His conclusion is that for small sites this is a good approach.

Related

Add Quartz Job&Trigger to running Razor Pages application

I have a razor pages app that implements Quartz.NET to store jobs in a MySQL Database. The implementation works fine so far, I can connect to the db, store jobs and they are executed at the specified times. The issue I'm having currently is that I need to schedule and execute jobs based on user inputs(without restarting the app) and that I can't get to work. I'm very new to Quartz&Asp.net and I haven't been coding for very long either, so apologies if I've made any stupid mistakes.
I've read somewhere that I shouldn't initialize multiple schedulers so I've tried storing the scheduler object I've got so I can access and use it later. However when I try to access it from another class later then I get a Null reference exception. Tbh, this feels like it shouldn't even work so I'm not surprised it doesn't...can anyone please look at my code below and tell me if this can work? Or is there a better way to do this?
I've found one other solution where they basically create a job on startup that periodically checks a db for new jobs and adds them to the scheduler. I guess that would work, seems a bit clunky, though. Plus it's from 10 years ago so maybe there's a better way today? How to add job with trigger for running Quartz.NET scheduler instance without restarting server?
One other idea I've had was to open(and close) a new app whenever I need to create a job. I'm not sure I like that idea but seems less resource intensive than the recurring job described above. Would that be a viable option?
The code for my current solution:
Scheduler:
//Creating Scheduler
Scheduler = await schedulerFactory.GetScheduler();
Scheduler.JobFactory = jobFactory;
var key = new JobKey("Notify Job", "DEFAULT");
if (key == null)
{
//Create Job
IJobDetail jobDetail = CreateJob(jobMetaData);
//Create Trigger
ITrigger trigger = CreateTrigger(jobMetaData);
//Schedule Job
//await Scheduler.ScheduleJob(jobDetail, trigger, cancellationToken);
await Scheduler.AddJob(jobDetail, true);
}
//Start Scheduler
await Scheduler.Start(cancellationToken);
//Copying the scheduler object into a different class where it's easier to access.
ScheduleStore scheduleStore = new ScheduleStore();
scheduleStore.tempScheduler = Scheduler;
ScheduleStore:
public class ScheduleStore
{
public IScheduler tempScheduler { get; set; }
public ScheduleStore()
{
}
}
runtime Scheduler:
public class RunningScheduler : IHostedService {
public IScheduler scheduler { get; set; }
private readonly JobMetadata jobMetaData;
public RunningScheduler(JobMetadata job)
{
ScheduleStore scheduleStore = new ScheduleStore();
this.scheduler = scheduleStore.tempScheduler;
this.jobMetaData = job;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
IJobDetail jdets = CreateJob(jobMetaData);
if (jobMetaData.CronExpression == "--")
{
ITrigger jtriggz = CreateSimpleTrigger(jobMetaData);
//the next line throws the exception.
await scheduler.ScheduleJob(jdets, jtriggz, cancellationToken);
//It's definitely the scheduler that's throwing the null pointer exception.
}
// the else does basically the same as the if, only with a cron trigger instead of a simple one so I've omitted it.
I see that you are using a hosted service. Have you noticed that Quartz has that support already built-in?
Quartz cannot handle new jobs "new code that runs" dynamically, but triggers for sure. You just need to obtain a reference to IScheduler and then you can add new triggers pointing to existing job or just call scheduler.TriggerJob which will call your job once with given parameters (job data map is powerful feature to pass execution parameters).
I'd advice checking the GitHub repository and its examples, there a specific ones for different features and ASP.NET Core and worker integrations.
Generatlly Quartz already has database persistence support which you can use. Just call scheduler methods to add jobs and triggers - they will be persisted and available between application restarts (and take effect immediately without the need for restart).

Online Users on ASP.NET Application

Saw so many articles and links related to the same concept.
Counting online users using asp.net
Is there any ASP.NET application to monitor online user and page view log report?
Mine is little different. My application is MVC4 and using SimpleMembershipProvider. I am not sure why GetNumberOfUsersOnline is not working.
https://msdn.microsoft.com/en-us/library/system.web.security.membership.getnumberofusersonline(v=vs.110).aspx
Any Ideas ? How to do this in a easy and efficient way. I am just using this in only one place in my website.
I found this online it it looks like it will work for you. Just add this code to your Global.asax.cs file:
protected void Application_Start(Object sender, EventArgs e)
{
Application.Lock();
Application["UserCount"] = 0;
}
protected void Session_Start(Object sender, EventArgs e)
{
Application.Lock();
Application["UserCount"] = (int)Application["UserCount"] + 1;
Application.UnLock();
}
protected void Session_End(Object sender, EventArgs e)
{
Application.Lock();
Application["UserCount"] = (int)Application["UserCount"] - 1;
Application.UnLock();
}
Then when you need to access the user count you can use:
var count = (int)Application["UserCount"];
You can use signalR to track connected users. Using this you can get count online efficiently & Real Time and also track connected users information. You can put condition to track logged in users also. So, Go with latest technology. You can implement with existing MVC application.
You can refer this official tutorial for the same.
http://www.asp.net/signalr
Another nice solution that is pleasantly orthogonal to your code is Google Analytics. http://www.google.com/analytics/ Using GA, you can see a real-time display of active users on your website. It also helps that you have a history over time and can see peaks and valleys of user activity.
From Microsoft documentation:
GetNumberOfUsersOnline returns the number of users for the current ApplicationName where the last-activity date is greater than the current time less the UserIsOnlineTimeWindow. The last-activity date/time stamp is updated to the current date and time when user credentials are validated by way of the ValidateUser or UpdateUser method or when a call to a GetUser overload that takes no parameters or one that uses the userIsOnline parameter to specify that the date/time stamp should be updated.
You can see that GetNumberOfUsersOnline depends on multiple parameters and isn't efective.
As a workaround I suggest that you could nherits SqlMembershipProvider and override the GetNumberOfUsersOnline(), so you cam implement your logic here.
public class MyMembershipProvider : SqlMembershipProvider
{
public override bool ValidateUser(string username, string password)
{
if (base.ValidateUser(username, password))
{
// successfully logged in. add logic to increment online user count.
return true;
}
return false;
}
public override int GetNumberOfUsersOnline()
{
// add logic to get online user count and return it.
}
}
Just decrement online user count logic in user log out
If you want track visitors and pages visited, here some idea:
track with httpmodule
Visitor Real-time Session
You may try to read the performance counters listed here :
Current Anonymous Users is the number of anonymous IIS users
Current Non-Anonymous Users is the number of authorized IIS users

Providing Download Progress Within SignalR Hub

I have an ASP.Net website where I am downloading a large zip file to the server from a remote site. This file is not transferred to the client, but will remain on the server. I would like to provide progress updates to the user using SignalR. When I use the code below:
public class InstallController : Hub
{
public void Send( string message )
{
Clients.All.AddMessage( message );
}
public void FileDownload()
{
WebClient client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler( client_DownloadProgressChanged );
client.DownloadFileCompleted += new AsyncCompletedEventHandler( client_DownloadFileCompleted );
client.DownloadFileAsync( new Uri( "http://someserver.com/install/file.zip" ), #"\file.zip" );
}
/* callbacks for download */
void client_DownloadProgressChanged( object sender, DownloadProgressChangedEventArgs e )
{
double bytesIn = double.Parse( e.BytesReceived.ToString() );
double totalBytes = double.Parse( e.TotalBytesToReceive.ToString() );
double percentage = bytesIn / totalBytes * 100;
this.Send( String.Format( "Download progress: {0}%", percentage.ToString() ) );
}
void client_DownloadFileCompleted( object sender, AsyncCompletedEventArgs e )
{
this.Send( "Finished downloading file..." );
}
}
I get the exception:
An exception of type 'System.InvalidOperationException' occurred in
System.Web.dll but was not handled in user code
Additional information: An asynchronous operation cannot be started at
this time. Asynchronous operations may only be started within an
asynchronous handler or module or during certain events in the Page
lifecycle. If this exception occurred while executing a Page, ensure
that the Page is marked <%# Page Async="true" %>. This exception may
also indicate an attempt to call an "async void" method, which is
generally unsupported within ASP.NET request processing. Instead, the
asynchronous method should return a Task, and the caller should await
it.
I've seen several mentions to use the HttpClient instead of the WebClient, but I don't see how to get the progress from that.
"It's All About the SynchronizationContext"
http://msdn.microsoft.com/en-us/magazine/gg598924.aspx
This phrase is becoming quite common since the addition of new technology and features in .NET.
Briefly.. There are several components, such as BackgroundWorker and WebClient, thats hiding the SynchronizationContext to the capture and usage, it means that you need to respect the life cycle of requests, the life cycle of ASP.NET components.
Speaking specifically, the HTTP methods (GET and POST) always keep working in the same way, the client submits a HTTP request to the server, then the server returns a response to the client, and the application will try to ensure that this occurs, the SynchronizationContext of ASP.NET was designed for this.
More information:
http://codewala.net/2014/03/28/writing-asynchronous-web-pages-with-asp-net-part-3/
Which ASP.NET lifecycle events can be async?
http://evolpin.wordpress.com/2011/05/02/c-5-await-and-async-in-asp-net/
Even the requests using SignalR contains the same ASP.NET SynchronizationContext, because of it you need to work "outside" the current SynchronizationContext or use it in the right way.
SignalR was designed to use asynchronous programming, using TPL as default, you can take benefits of it, check in http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-server#asyncmethods and http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-server#asyncclient
You can solve your problem in many ways.
If you want to use SignalR to show the progress.. I would do something like the code below (I'm still using .NET 4.0, bu it is more easy with .NET 4.5 with TaskAsync methods).
public Task<string> FileDownload()
{
var client = new WebClient();
client.DownloadProgressChanged += (sender, args) => client_DownloadProgressChanged(sender, args, this.Context.ConnectionId);
client.DownloadFileAsync(new Uri("https://epub-samples.googlecode.com/files/cc-shared-culture-20120130.epub"), #"C:\temp\file.zip");
var result = new TaskCompletionSource<string>();
AsyncCompletedEventHandler clientOnDownloadFileCompleted = (sender, args) =>
{
client.Dispose();
if (args.Error != null)
{
result.SetException(args.Error); //or result.SetResult(args.Error.Message);
return;
}
result.SetResult("Downloaded");
};
client.DownloadFileCompleted += clientOnDownloadFileCompleted;
return result.Task;
}
private static void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e,
string connectionId)
{
GlobalHost.ConnectionManager.GetHubContext<SomeHub>()
.Clients.Client(connectionId)
.NotifyProgress(e.ProgressPercentage);
}
Keep in mind that this is just an example, you could improve the way they treat the disconnection, and cancellation, among other things that can occur (depends on your application logic).
Also it is possible to use a "workaround" (not recommended):
Fire and forget async method in asp.net mvc
How to execute async 'fire and forget' operation in ASP.NET Web API
The code would be very similar to the above.

How to get HealthVault to work with multiple ApplicationID in same application

We may never know why Microsoft decided to limit developers by making HealthVault applications constrained to a single web/app.config entry for a HealthVault application. However I need to be able to make 2 (or more) HealthVault ApplicationID’s work with one ASP.NET website? I’m looking for an effective and reliable way to do this.
I won’t go into the details of the reasoning behind 2 different HealthVault applications, but other than to say we need it to work. I still cannot login correctly with MSDN Forums (think infinite redirection sign in loop) so I am hoping for a post here that will help me.
I did contact a HealthVault developer on how to achieve this however the developer gave a suggestion that I don’t believe would be reliable (if I’m wrong let me know).
The developer’s suggestion was to do the following in code when you needed to connect to HealthVault, but prior to connecting:
ConfigurationSettings.AppSettings[“ApplicationId”] = “[YOUR APP ID]”;
The problem is that this is a static property and I do see this as an issue as our web application will have different users accessing both HealthVault applications at the same time.
Does anyone have any suggestions to make 2 (or more) HealthVault ApplicationID’s work with one ASP.NET website? I’m looking for an effective and reliable way to do this.
There is a way to dynamically switch app ids on runtime. Both applications must be created, both certificates must be installed. Few things to keep in mind. For every authenticated connection, user will be granted a token (aka wctoken). This token is consumed when user is redirect back from Live ID (in case live id is used...) by your redirect.aspx page (assuming your redirect page inherits from HealthServiceActionPage.This means that everytime you switch applications, you must redirect user back to Live ID with new app id to receive new token.
Here is code sample that can be user to dynamically change settings:
public class ConfigurationManager : HealthWebApplicationConfiguration
{
private string appid;
public ConfigurationManager(string appid)
{
this.appid = appid;
}
public override Guid ApplicationId
{
get
{
return AppManager.Current.GetCurrentAppId(this.appid);
}
}
}
public class AppManager
{
private static readonly Object lck = new Object();
public Guid? App;
public static AppManager Current
{
get
{
AppManager mgr = null;
if (_current == null)
{
lock (lck)
{
mgr = new AppManager();
}
}
return mgr;
}
}
private static AppManager _current;
public Guid GetCurrentAppId(string id)
{
return new Guid(id);
}
}
Usage:
ConfigurationManager cm = new ConfigurationManager(your-app-id-here);
HealthWebApplicationConfiguration.Current = cm;

How to manage COM access in ASP.NET application

I have built ASP.NET app. I need to utilize a third party COM object SDK for managing documents. The application's name is "Interwoven". According to the COM object documentation you should "Never create more than one IManDMS object per application".
So, I decided to create one instance of the IManDMS object and store it in an Application variable. Here is the function that I use to retrieve a IManDMS object:
public static IManDMS GetDMS()
{
IManDMS dms = HttpContext.Current.Application["DMS"] as IManage.IManDMS;
if (dms == null)
{
dms = new IManage.ManDMSClass();
HttpContext.Current.Application["DMS"] = dms;
}
return dms;
}
…
// Here is a code snippet showing its use
IManage.IManDMS dms = GetDMS();
string serverName = "myServer";
IManSession s = dms.Sessions.Add(serverName);
s.TrustedLogin();
// Do something with the session object, like retrieve documents.
s.Logout();
dms.Sessions.RemoveByObject(s);
…
The above code works fine when only one person is requesting the .ASPX page at a time. When 2 users concurrently request the page I get the following error:
[Sessions ][Add ]Item "SV-TRC-IWDEV"
already exists in the collection
Apparently you cannot add more than one session to the IManDMS object, with the same name, at the same time. This means I can only have one session open, for the entire app, at any given time.
Is there anyone who has experience with a similar issue and can offer a suggestion on how to get around this problem, assuming I can't create more than one IManDMS object per app?
Thanks.
You can use a lock to make sure only one session access the object at the same time. I am not sure what IManDSM does, but based on your code, it looks like the error is caused by
IManSession s = dms.Sessions.Add(serverName);
You are adding the same name to the Sessions collection. Can you try adding a different name like SesssionID to the Sessions collection?
In ASP.Net, you can safely think of each session as it's own application. Here is logic I have used with the IManage SDK kit for years to manage sessions in Global.asax file:
void Session_Start(object sender, EventArgs e)
{
// Code that runs when a new session is started
ManDMS clientDMS =
new ManDMSClass();
clientDMS.ApplicationName = "My App Name";
IManSession clientSession =
clientDMS.Sessions.Add(
ConfigurationManager.AppSettings["DMS Server"]);
clientSession.MaxRowsForSearch = 750;
clientSession.AllVersions = false;
// Save the configured session for use by the user
Session.Add("Client.Session", clientSession);
}
void Session_End(object sender, EventArgs e)
{
// Code that runs when a session ends.
// Note: The Session_End event is raised only when the sessionstate mode
// is set to InProc in the Web.config file. If session mode is set to StateServer
// or SQLServer, the event is not raised.
IManSession clientSession =
(IManSession)Session["Client.Session"];
try
{
if (clientSession.Connected)
clientSession.Logout();
}
catch (System.Runtime.InteropServices.COMException cex)
{
// Ignore COMException if user cannot logout
}
clientSession.DMS.Close();
}
The benefit to this is that the session setup / tear-down is linked to the ASP.Net session, which adequately manages the session, and provides some great speed benefits to the user.
Whenever you need to access the Session object, just use this code on your ASP.Net pages or postbacks:
IManSession clientSession =
(IManSession)Session["Client.Session"];

Resources