Calling a method on Session Timeout? - asp.net

I have a website with multiple pages. Each page has a "Save as Draft" method that will save the users progress. I have a session timeout of 40 minutes, and I was wondering how I would make it so that whenever the session is expired, the page automatically calls "Save as Draft" (so it's not lost)
How would I start on doing something like this?

You can't: the session expires server side and cannot send a message to the browser (it doesn't even know whether the user still sees the page or has closed the browser).
You could set a client-side timeout to save the page (as draft) to the server, but this has the effect of refreshing the session! So maybe just do an automatic "save as draft" every 5 - 10 minutes, thereby keeping your session (plus a draft copy of the work done).

In .Net, you can do it via Global.asax in Session_End(object sender, EventArgs e) routine. I use this to do some resource release work.

you can call function in global.asax when session expire's like this
Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)
If Not Session.Item("sessionid") Is Nothing Then
If Session.Item("sessionid").ToString = "1" Then
ClsUtilities.CustomerLogout()
ElseIf Session.Item("sessionid").ToString = "2" Then
ClsUtilities.Logout()
End If
End If
End Sub

Related

Send Email without waiting for the action to finish execution after Button click ASP.NET

In short, remaining in the HTTP context, I would like the user, after clicking on an order completion button, not to wait for the mails to be sent before being sent back to a "thak you page".
I saw that HostingEnvironment.QueueBackgroundWorkItem could help me with this but there is always the risk of it being killed by IIS recycling.
What is the best solution to do this?
I know the best would be to develop a separate console solution but it wouldn't be worth it for 3/4 emails, alternatively I could consider speeding it up by making them asynchronous?
Protected Sub btnConcludiOrdine_Click(sender As Object, e As System.EventArgs) Handles btnConcludiOrdine.Click
If IsValidOrder(Me._cart, msg) Then
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(Me._cart, Me._lang) '2 Mails
mail.SendProductNotice() '2 Mails
End If
Else
Response.Redirect("*Error URL*")
End If
End Sub
The way you approach this is as suggested – start a task, or so called new processor thread.
So, what you would do is break out the code – this even works if code behind is for a web form.
So, the first step is to move out the “slow” parts, or the parts we want to run separate.
The main issue is that to start/launch/want/desire/achieve a brand new processor thread?
The sub call CAN ONLY PASS ONE “parameter” and the sub can only accept one parameter!!!!!
I note in your case that routine needs two values.
However, that “one parameter” can be a array of “many” values, or even a collection or whatever. In our case we pass the two values.
So just keep in mind that what you call can NOT update or “use” the values of controls on the form – the instance of that form will go out of scope.
But we can of course PASS the values you need. This will allow that routine to run 100% independent of the web form.
I also VERY strong suggest that if you DO place the sub in the same web page code behind? You should/can mark that sub as shared. Doing so will allow the compiler to get mad at you and spit out errors if that routine say tries to use or update a control value on the form.
However, it is MUCH better is to place this sub in a separate standard code module out side of the web forms code behind.
Regardless of above, we can now re-write the code we have as this:
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
Dim myInfo(1) as object
myInfo(0) = me.cart
myInfo(1) = me_._lng
Call MyUpdateQ(myInfo)
End If
' bla bla lba
Shared Sub MyUPdateQ(p() as object)
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(p(0),p(1)
mail.SendProductNotice() '2 Mails
End Sub
Ok, so far, we not achieved much, but we re-writing to accept the ONE array is KEY here.
So, now now make sure the above runs/works and is all happy.
Now, because we moved out the "work load" to that one routine, it is now a simple matter to start a thread.
Now, Our above code becomes this:
Protected Sub btnConcludiOrdine_Click(sender As Object, e As System.EventArgs) Handles btnConcludiOrdine.Click
If IsValidOrder(Me._cart, msg) Then
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
Dim myInfo(1) as object
myInfo(0) = me.cart
myInfo(1) = me_._lng
Dim MyThread As New Thread(New ParameterizedThreadStart(AddressOf MyUpdateQ))
MyThread.Start(myInfo)
End If
Else
Response.Redirect("*Error URL*")
End If
End Sub
Shared Sub MyUPdateQ(p() as object)
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(p(0),p(1)
mail.SendProductNotice() '2 Mails
End Sub
That is it. Now when you click your button it will wait ZERO time, since the long running routine is now going to run 100% as a separate thread. And this will also mean that when the user clicks the button - the page will respond instant and post back to user will be done. So if that thread takes 6 seconds, or even 25 seconds, the user will not notice this delay.
Just push your sending mail logic in Task and if you are not interested in result don't await it. c# syntax
Task.Run(() => SendEmail());

allowing only one instance of page

i have a question about allowing only one pageinstance in all clientsessions. So if a client ask for the unique page and there is already a session with this page. The client which ask for the page should get a messagebox, which say "This page is already in use"
I read about this problem on forums. Many people say "it's impossible to get out if the client have close the browser (including the unique page)".
Is that true about the serverside?
Is there a way to handling this problem?
Is use Asp.net with Vb.net
i hope anyone understand me. My english is bad.
The problem with keeping track of whether the current user is still using the page can be managed by having an UpdatePanel with a Timer which then sets an application variable time every 500ms (or whatever interval you want)
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
Application["PageLastUsed"] = DateTime.Now;
End Sub
and redirect users if the page is in use
TimeSpan duration = DateTime.Now - (DateTime)Application["PageLastUsed"];
if (duration.TotalSeconds < 2)
{
Response.Redirect("InUsePage.aspx");
}
See timer info here http://msdn.microsoft.com/en-us/library/cc295400.aspx

Why does my ASP.NET keep-alive logic not work?

In order to ensure that my session always stays on, I created a simple stayalive.aspx page. The header of the page contains metadata to refresh the page every 5 minutes.
In my page-load logic, I simply set a value into session.
protected void Page_Load(object sender, EventArgs e) {
System.Web.HttpContext.Current.Session["Alive"] = 1;
}
My understanding is that as long as you keep putting a value in the session, the session will continue to stay alive. However, this does not seem to be working. I still get a session timeout in about 30 minutes.
I am wondering if anyone has any insight on why is not working.
Note that the sessionstate as well as forms authentication timeout values in web.config are set to 300 (5 hours).
One thought I had was, instead of setting the same value on the session, I set a different value each time:
System.Web.HttpContext.Current.Session["Alive"] = DateTime.Now;
Do you think this would help?
Adding a value in to the session is not required to session alive. If you keep on refreshing the aspx page, session should automatically extend.

Proper way to skip page execution after Response.RedirectToRoute

I'm writing an asp.net 4.5 application using the new routing features. I have a page that displays some information about an item. In the Page_Load event I check the route data (item id) and user permissions, and if something isn't right (e.g. the id is for a deleted item) I use Response.RedirectToRoute to send them packing, right back to the home page. Do not pass GO, do not collect $200.
This made perfect sense until I tried to access a deleted item and instead of the home page I got an error page. I did some digging and discovered that even after I use RedirectToRoute (unlike the standard Redirect method) the rest of the page code continues to execute, which at the very least seems wasteful (since I'm just going to throw away the results) and throws errors when the necessary data doesn't exist.
I did a little more SO mining and discovered the incredible evil that is Response.End(). It does what I need, but even the MSDN page tells me that Response.End is the bastard child of an ancient accursed language and isn't fit to see the light of day. The primary objection seems to be the fact that Response.End throws an exception, and that's bad for performance. I'm not the most experienced developer, so I don't understand the issue entirely, but I have trouble believing that throwing an exception is more expensive than loading the entire web page. The workarounds seem rather complex and excessive for a task so simple, especially since most pages require some kind of validity check.
What am I supposed to do in this situation? Use Response.End and beg forgiveness for my insolence? Cobble together some ugly workaround? Or is my perspective on the problem all wrong to begin with? I'd really like to know.
Update: Now that I've thought it over a bit more, I wonder if I do have the wrong perspective on the problem. Perhaps an immediate redirect is the not the best response for the user experience. Would I be better off wrapping all the controls in a panel, and using something like this?
Private Sub Page_Init(sender As Object, e As EventArgs) Handles Me.Init
'Validation Code
If notValid Then
ControlsPanel.Visible = false
ErrorPanel.Visible = true
End If
End Sub
RedirectToRoute is actually wraps Response.Redirect passing false for ending the request - hence, the request continues. You can use HttpApplication.CompleteRequest as immediate call to terminate the request so that next application events would not be invoked.
Response.End (and other Redirect variation) throws ThreadAbortException to abort the request processing thread which is really a bad way to stop request processing. In .NET world, exception processing is always considered expensive because CLR then needs to search up the stack all the way up for exception processing blocks, create stack trace etc. IMO, CompleteRequest was introduced in .NET 1.1 to avoid the same which actually relies on setting flag in ASP.NET infrastructure code to skip further processing except EndRequest event.
Yet another (and better) way is to use Server.Transfer and avoid client round-trip for setting redirect all together. Only issue is that client would not see the redirected URL in the browser address bar. I typically prefer this method.
EDIT
CompleteRequest wouldn't never work in page case where subsequent page events would be still invoked because page being a handler, all its events happens within a single (and current) application event ProcessRequest. So only way seems to be setting a flag and check that flag in overrides such as Render, PreRender, RaisePostBackEvent etc.
From maintenance perspective, it make sense to have such functionality in base page class (i.e. maintaining the flag, offering CompleteRequest method to subclasses and overriding life cycle event methods). For example,
internal class PageBase: System.Web.UI.Page
{
bool _requestCompleted;
protected void CompleteRequest()
{
Context.ApplicationInstance.CompleteRequest();
_requestCompleted = true;
}
protected override void RaisePostBackEvent(IPostBackEventHandler sourceControl,
string eventArgument)
{
if (_requestCompleted) return;
base.RaisePostBackEvent(sourceControl, eventArgument);
}
protected internal override void Render(HtmlTextWriter writer)
{
if (_requestCompleted) return;
base.Render(writer);
}
protected internal override void OnPreRender(EventArgs e)
{
if (_requestCompleted) return;
base.OnPreRender(e);
}
... and so on
}
I may be going out on a limb by not answering the question directly, but I liked seeing your update regarding user experience. I prefer your suggested approach.
I like to give a 410 error for id's that are not valid and extend it a bit with (translated from C#):
Protected Sub ItemDoesNotExist()
'item does not exist, serve up error page
ControlsPanel.Visible = False
ErrorPanel.Visible = True
'add meta tags for noindex
Dim mymeta As New HtmlMeta()
mymeta.Name = "robots"
mymeta.Content = "noindex"
Page.Header.Controls.Add(mymeta)
'RESPOND WITH A 410
Response.StatusCode = 410
Response.Status = "410 Gone"
Response.StatusDescription = "Gone"
Response.TrySkipIisCustomErrors = True
'important for IIS7, otherwise the Custom error page for 404 shows.
Page.Title = "item gone"
End Sub

How does one discard a session variable while closing Web Page?

We are following a procedure in our work while developing a web page, is to bind page to one or more session variables, these session variables are used only for that page, to hold current processing objects, so while closing page no need for them.
How could I discard these session variables while closing page?
Any suggestions regarding that technique or how to solve that problem?
There is no server-side event that is raised when a page is left/closed. Also the Session_End event (mentioned in other answers) is not called when a page is left, since the user might navigate to other pages of the same web application (and therefore the session will continue to exist).
I can think of 3 possible ways to solve (or work around) this issue:
1 - use ViewState to store data with page-scope. This is what ViewState is made for, and unless you have a lot of data, it should not be a problem. If you have a lot of data, remember, that it will be serialized/deserialized and sent to the client/back to the server for every request (which may result in large requests and therefore bad performance).
2 - instead of putting the data into the session, put it into the Cache (with a low sliding expiration timeout). On your page, you can access your data in the same way as from the session, i.e. data = Cache["data"], but you have to be prepared that the data was removed from the Cache (you have to re-load it again from DB for example), if the time between two requests was bigger than the expiration time.
3 - use the client-side (javascript) onUnload event, and trigger some action (e.g. a ajax callback) to remove the data from the session. But I think the onUnload event is not reliable (it will not be fired in any case, e.g. when the browser is terminated by a crash or with the task manager, or if javascript is disabled).
If you use variables for only that page, store them in viewstate. ViewState is suitable for page scoped variables.
If you are using ASP.NET sessions (which you probably are), you can add a global.asax file to your soluting. In there this event-delegate is to be found (if not, create it):
protected void Session_End(object sender, EventArgs e)
{
}
.. In here you can clear your session collection.
protected void Session_End(object sender, EventArgs e)
{
Session.Clear();
}
This will be fired when the session expires or when a user clicks logout :)

Resources