Cache page but count hits - c#

I have an aspx page which counts every visit and creates a cookie. But if I use OutputCache page counts only the first visitor who requested the page. How can I prevent this bug?
Page directive:
<%# OutputCache Duration="1200" VaryByParam="mode;page;sid;tid" %>
Codebehind:
protected void Page_Load(object sender, EventArgs e)
{
//Load single post data
#region Hit Counter
//hit counter lasts during session
if (Session["LastHit" + postId] == null)
{
cmmnd.CommandText = "UPDATE Posts SET Hits=Hits+1 WHERE PostID=#PostID;";
cmmnd.ExecuteNonQuery();
Session["LastHit" + postId] = 1;
}
#endregion
}

This isn't a bug, but by design. The page is not re-processed if it is in the cache and "the cached output is still valid". You can run code during this validation phase and this could help you perform the task of counting the visits and adding it to a cookie. The following might be of help:
Caching ASP.NET Pages
How to: Check the Validity of a Cached Page

What is the point of this count? Marketting information? Use a lightweight analytics counter from script or an image in the page, not mixed in with generating the page itself. The bug is that you're adding extra work to the busier requests, instead of giving the extra work its own request.

Related

Session is null in AcquireRequestState when loading virtual directory name in browser, but not null when loading Default.aspx

I have an ASP.NET 4.0 WebForms application. I need to access HttpContext.Current.Session and set a value in the AcquireRequestState event (or an event after it) in Global.asax, and I've found a peculiar behavior.
Let's say I have a virtual directory in IIS (version 7 in my case) called Foo. In that I have Default.aspx as the home page. A sample Global.asax file is below:
<%# Application Language="C#" %>
<script runat="server">
void Application_AcquireRequestState(object sender, EventArgs e)
{
HttpContext.Current.Session["key"] = "value";
}
</script>
When I visit http://localhost/Foo/Default.aspx in my browser, it works just fine. When I visit http://localhost/Foo/ I get a NullReferenceException where I set the value on the session. The only change is the URL in the browser. They end up hitting the same page, but the framework behaves differently based on whether or not the URL contains just a folder name, or if it contains an aspx file.
Checking if (HttpContext.Current.Session != null) is not an option for me, because I need to set a value on the session with every request, which is non negotiable.
Is there a config setting in IIS that I'm missing, or is this a bug/forgotten feature?
An answer for another question hinted at the fact IIS does not load the session for every kind of request, for example style sheets don't need a session. Maybe this behavior is happening because IIS can't tell ahead of time if that folder name will result in executing an aspx file or if it will deliver a static HTML file?
Update: I even tried re-ordering the default documents that IIS looks for so that "default.aspx" was at the top of the list, e.g.
default.aspx
Default.asp
Default.htm
...
And I am still getting the same problem.
Update:
The event handler is only getting fired once because it is resulting in a NullReferenceException. I've done some additional reading and I know ASP.NET triggers these events for every request, even for CSS or JavaScript files. Additionally, the session object is not loaded for static files because there is not code that accesses the session, thus no need to load the object. Even so, the very first request is the request for the web page, which will need the session, and the session is null.
#DmytroShevchenko asked:
First add a guard check if (HttpContext.Current.Session != null) so that there is no NullReferenceException thrown. Then try to see, maybe the event will be fired a second time, with a session available.
Modified code:
void Application_AcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Session != null)
{
HttpContext.Current.Session["key"] = "value";
}
}
I set a break point at the if statement. I saw this event fire 4 times:
session is null
session is null
session not null
session is null
When continuing to step through the code each time, only when it started executing Default.aspx and its code-behind did I have a session available. I actually had the web page open in Firefox and was monitoring the network requests. The first request was for http://localhost/Foo/.
Next I set a breakpoint in Application_BeginRequest as well and got the following events:
BeginRequest
AcquireRequestState
BeginRequest
AcquireRequestState
BeginRequest
AcquireRequestState (session is not null)
Execute Default.aspx (/Foo returns a response to the browser)
BeginRequest
AcquireRequestState (session is null again)
At #9 the AJAX request in the browser to http://localhost:54859/8fad4e71e57a4caebe1c6ed7af6f583a/arterySignalR/poll?transport=longPolling&connectionToken=...&messageId=...&requestUrl=http%3A%2F%2Flocalhost%2FFoo%2F&browserName=Firefox&userAgent=Mozilla%2F5.0+(Windows+NT+6.1%3B+WOW64%3B+rv%3A41.0)+Gecko%2F20100101+Firefox%2F41.0&tid=4&_=1445346977956 is hanging waiting for a response.
I found a discussion about the differences between serving a page by its explicit URL and serving a default document.
With MVC and WebAPI, a new HttpModule was introduced: ExtensionlessUrlHandler. I believe that your event firing multiple times (and only once with session being available) may be caused by this module or other (re)routing logic of ASP.NET that actually redirects ASP.NET to handle Default.aspx.
Also, as you yourself mentioned, these events can be triggered for static file requests.
The bottom line is, you should not rely on session being available every time your event is fired. But it is safe to assume that you can access session at least once when serving an ASP.NET page. Hence, this is how your code should look like:
void Application_AcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Session != null)
{
HttpContext.Current.Session["key"] = "value";
}
}
What is happening is that certain events that occur prior to transferring the request to the page, trigger this event. Similarly, this event can be raised by other than ASPX pages. I think you need to do a few things:
set AutoEventWireUp to false in your page's code (in default.aspx). It seems odd and unrelated, but apparently this can resolve your issue.
check for null. Really. Because from all events, you are only interested in those events that originated from an ASPX page (or similar) that actually have a session state. Since every true request from a user always creates a session, you can simply filter for these without worrying that you miss certain events. As you have seen, at every cycle, there is always at least one event hit that has a set Session.
use PostAcquireRequestState, it is the more natural thing to use (or use PreRequestHandlerExecute), because on that event, all state is guaranteed to be set and filled.
I think your request url is not contain ".aspx", right?
The IIS7+ version has the config whitch will not use the "SessionStateModule" when it think the request handler is not managedHandler
So the solution is easy
Find your web.config, then add the property
<modules runAllManagedModulesForAllRequests="true">
....
</modules>
runAllManagedModulesForAllRequests="true" tell asp.net use all modules anyway
hope helpfull
When the resource has compilation error then the session will be null even in Application_PostAcquireRequestState

Get users current page when session times out

Lots and lots of examples out there as to how to set and redirect when the session times out. But nothing that I could find for this situation.
A typical situation where a timeout control monitors the session timeout and displays a window warning the user of that when there is 1 minute left. When the session times out, the user is redirected to a page (sessionexpired.aspx) that clears the session and displays information informing the user that they are required to login again.
I would like to log the page the user was on when the session actually timed out and do that in the code behind of the sessionexpired.aspx page.
Any help would be very welcome!
Try looking at the Referer from the current Request object. It's usually found using this:
Request.UrlReferrer
I hope this helps.
You can use one of these code examples to get the current page the user is on:
// Returns something like "http://www.example.com/myUrl/MyPage.aspx"
string page = HttpContext.Current.Request.Url.AbsoluteUri;
// Returns something like "/myUrl/MyPage.aspx"
string page = HttpContext.Current.Request.Url.AbsolutePath;
// Returns something like "MyPage.aspx"
string page = HttpContext.Current.Request.Url.AbsolutePath.Substring(url.LastIndexOf('/') + 1);
So wherever your code is to log you out when your session expires (probably in your master page), just add one of these lines.
Once you have it, you can add it as a query string parameter to your login page or you can save it in a database.

Redirecting to another page on Session_end event

I would like to auto-redirect to login page when session time outs.
In web.config file, i have the following code
<configuration>
<system.web>
<sessionState mode="InProc" timeout="1"/>
</system.web>
</configuration>
In Global.asax file-
protected void Session_End(object sender, EventArgs e)
{
Response.Redirect("LoginPage.aspx");
}
But after time-out, i am receiving the following error:
HttpException was unhandled by user code.
Response is not available in this context.
Any clue to solve this issue?
Session_End is called when the session ends - normally 20 minutes after the last request (for example if browser is inactive or closed).
Since there is no request there is also no response.
I would recommend to do redirection in Application_AcquireRequestState if there is no active session. Remember to avoid loops by checking current url.
Edit: I'm no fan of .Nets built in authentication, Example goes in Global.asax:
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
try
{
string lcReqPath = Request.Path.ToLower();
// Session is not stable in AcquireRequestState - Use Current.Session instead.
System.Web.SessionState.HttpSessionState curSession = HttpContext.Current.Session;
// If we do not have a OK Logon (remember Session["LogonOK"] = null; on logout, and set to true on logon.)
// and we are not already on loginpage, redirect.
// note: on missing pages curSession is null, Test this without 'curSession == null || ' and catch exception.
if (lcReqPath != "/loginpage.aspx" &&
(curSession == null || curSession["LogonOK"] == null))
{
// Redirect nicely
Context.Server.ClearError();
Context.Response.AddHeader("Location", "/LoginPage.aspx");
Context.Response.TrySkipIisCustomErrors = true;
Context.Response.StatusCode = (int) System.Net.HttpStatusCode.Redirect;
// End now end the current request so we dont leak.
Context.Response.Output.Close();
Context.Response.End();
return;
}
}
catch (Exception)
{
// todo: handle exceptions nicely!
}
}
If you are using something like FormsAuthentication for maintaining the security of your application, then this part (that part that you are trying to do) will be done for you. If FormsAuthentication discovers that a user's session has expired it will redirect him or her back to you login page.
Second, don't rely too much on Session_End because it will never trigger if you change session provider from InProc to SQLServer or other out of process provider.
You can use session property IsNewSession to detect whether it is session timeout or not.
The ASP.NET HttpSessionState class has IsNewSession() method that returns true if a new session was created for this request. The key to detecting a session timeout is to also look for the ASP.NET_SessionId cookie in the request.
Definitely I too agree that we should put the below code in some so called a custom BasePage, which is used by all pages, to implement this effectively.
override protected void OnInit(EventArgs e)
{
base.OnInit(e);
if (Context.Session != null)
{
if (Session.IsNewSession)
{
string CookieHeader = Request.Headers["Cookie"];
if((CookieHeader!=null) && (CookieHeader.IndexOf("ASP.NET_SessionId") >= 0))
{
// redirect to any page you need to
Response.Redirect("sessionTimeout.aspx");
}
}
}
}
check this link for more explanations if you want to put the above code in a base page class .
You should use Application_AcquireRequestState
You'll find that Application_AuthenticateRequest no longer has a session context (or never had one).
In my research into this I cam across this post which solved it for me.
Thanks go to Waqas Raja.
asp.net: where to put code to redirect users without a session to the homepage?
I think you are getting "Response is not available in this context" because the user is not making a request to the server, and therefor you cannot provide it with a response. Try Server.Transfer instead.
The easiest way what I feel is to use Meta information and get the trick working. Consider we have a page WebPage.aspx add the below code in the the WebPage.aspx.cs file.
private void Page_Load(object sender, System.EventArgs e){
Response.AddHeader("Refresh",Convert.ToString((Session.Timeout * 60) + 5));
if(Session[“IsUserValid”].ToString()==””)
Server.Transfer(“Relogin.aspx”);
}
In the above code, The WebPage.aspx is refreshed after 5 seconds once the Session is expired. And in the page load the session is validated, as the session is no more valid. The page is redirected to the Re-Login page. Every post-back to the server will refresh the session and the same will be updated in the Meta information of the WebPage.aspx.
you can simply do the following in web.config
<configuration>
<system.web>
<sessionState mode="InProc" timeout="1" loginurl="destinationurl"/>
</system.web>
</configuration>
Since, we can't redirect from Session_End as no response/redirect is present there.By using this you will be redirected to destinationurl when session will timeout.Hope this helps.

Handle session timeout loss of data

In a module I wrote I store in the session the items the user added to his cart.
How would you handle this situation: The user adds a new item to his cart after the session timedout?
I can redirect to the homepage but then I'm causing the redirect to happen even when the session wasn't in use. like when the user isn't logged in, or his cart was empty.
How do you handle session timeout in your applications?
In this case, I might not use the built in ASP.NET Session provider. Instead you could set a persistent cookie for the customer's shopping cart session with an encrypted ID that maps to a session stored in the database, which stores the contents of the cart.
This way, you don't need to worry about timeouts, the session will continue for the lifetime of the cookie.
(If you really need to use ASP.NET Session, maybe you've already got code that uses it, then you could set the timeout to a very large number and configure the Session state provider to use SQL Server.)
Your page can have a javascript timer running and when the time runs out the page is redirected to another page, say login, with query string parameters as flags. When the Login screen is called with x flag, then the shopping cart is saved to the database for safe keeping. The key is to have the timeout be shorter than the session timeout.
ADDED:
Javascript on each page:
<body onload="PageLoad();">
<script type="text/javascript">
function PageLoad()
{
var t = setTimeout("ExceuteTimeOutTransfer()", 15000);
}
function ExceuteTimeOutTransfer()
{
window.location.href = "http://localhost/webplaying/Login.aspx?timeout=Y";
}
</script>
Code Behind on Login Page:
protected void Page_Load(object sender, EventArgs e)
{
if (Request.QueryString["timeout"] != null)
{
if (Request.QueryString["timeout"].ToUpper() == "Y")
{
SaveCart();
}
}
}
private void SaveCart()
{
lblResult.Text = "Cart Saved";
}
An alternative would be to store the cart in a cookie.

Detect Session Timeouts / Distinguish Between First Visit & Session Timeout

When a user goes to the sign in page, I want to detect if their session timed out and was redirected to this page, so a friendly message could be displayed.
I set isTimeout = true when the session is a new session and when the cookie["ASP.NET_SessionId"] is not null. But isTimeout was set to true when if it was a first visit too. How do I distinguish the first visits from timeouts?
Thanks in advance!
In your Global.asax there is a method called Session_End to handle just this.
You can use this to add whatever functionality you need. Such as setting TempData["IsTimeout"] to true (if you are using ASP.NET MVC). This will then persist past the redirect and is accessible on your log in view. It will then be destroyed.
E.g. In your Global.asax.cs
protected void Session_End(Object sender, EventArgs e)
{
TempData["IsTimeout"] = true;
}
In your log in view:
<%: ((bool)(TempData["IsTimeout"] ?? false)) ? "For security reasons you were timed out, please log in again" : "" %>

Categories