Get last activity after timeout in ASP.NET WebForms - asp.net

I have an ASP.NET WebForms page with forms authentication. When users create a login, I use 'remember me' to create the authentication cookie.
What I now want to do is check the time of their last access. But LastLogin time is updated only when the user uses the login control (which they don't need to use when they have the authentication cookie on their machine), and LastActivity control is updated before any of my code runs.
It looks like the only way I can do this is to hook into the application event Application_AuthenticateRequest - right? Or is there some better way to do this?
Thanks!

Yes you will want to hook the FormsAuthenticationModule.Authenticate event. You can do this by adding a module to your web application. See the following sample module code.
public class BasicAuthenticateModule : IHttpModule
{
public BasicAuthenticateModule()
{
}
public void Dispose()
{
}
public void Init(HttpApplication context)
{
foreach (string name in context.Modules.Keys)
{
if (name == ApplicaionModules.FormsAuthentication)
{
FormsAuthenticationModule module = (FormsAuthenticationModule)context.Modules[name];
module.Authenticate += new FormsAuthenticationEventHandler(module_Authenticate);
break;
}
}
}
private void module_Authenticate(object sender, FormsAuthenticationEventArgs e)
{
}
}
Enjoy!

Instead I used the session_start event in Global.asax.
In there I've stored the current and previous session start DateTime's against the user in the DB (moving the current to the previous each time). This gets me the time of a user's previous session.
It might be better to use session_end - but that's not the time the user left the page, it's [timeout] time after their last activity - so this is a fairly good solution.

Related

How to count the number of times a website is visited and the number of online users in MVC

I need to find out the number of times a website has been visited and how many online users it has.
My code is this:
Global.asax
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Code that runs on application startup
Application["SiteVisitedCounter"] = 0;
//to check how many users have currently opened our site write the following line
Application["OnlineUserCounter"] = 0;
}
void Session_Start(object sender, EventArgs e)
{
// Code that runs when a new session is started
Application.Lock();
Application["SiteVisitedCounter"] = Convert.ToInt32(Application["SiteVisitedCounter"]) + 1;
//to check how many users have currently opened our site write the following line
Application["OnlineUserCounter"] = Convert.ToInt32(Application["OnlineUserCounter"]) + 1;
Application.UnLock();
}
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.
Application.Lock();
Application["OnlineUserCounter"] = Convert.ToInt32(Application["OnlineUserCounter"]) - 1;
Application.UnLock();
}
The HomeController class contains the following code.
I got an error on System.Net.Mime.MediaTypeNames.Application.
[HttpGet]
public ActionResult Index()
{
ViewBag.noofsitesvisited = "No of times site visited=" + System.Net.Mime.MediaTypeNames.Application["SiteVisitedCounter"].ToString();
ViewBag.onlineusers = "No of users online on the site=" + System.Net.Mime.MediaTypeNames.Application["OnlineUserCounter"].ToString();
}
You don't want to do it this way. One, reading and writing data from anything global in web environment is dangerous and inadvisable from the get go, and two, this will only store the count while the AppPool is active anyways. If the server restarts or the AppPool restarts or even just recycles, your counts all go away and you start over from zero.
If you want to store a count that needs to persist, then you need to use a persistent medium: database, text file, etc. Not only is this safer in general, it is also the only way to have a true persistent count.
That said, why not just use Google Analytics or some other form of website analytics. Not only are you reinventing the wheel, but actual analytics tracking will be more accurate and provide more useful statistics than anything you can do on your own.
You need to change the code in the controller as follows:
[HttpGet]
public ActionResult Index()
{
ViewBag.noofsitesvisited = "No of times site visited=" + HttpContext.Application["SiteVisitedCounter"].ToString();
ViewBag.onlineusers = "No of users online on the site=" + HttpContext.Application["OnlineUserCounter"].ToString();
}
In MVC Application variables are accessible via HttpContext

ASP.NET Session State Providers

Our ASP.NET app sends multiple Ajax requests in parallel on the same session. We also read/write to HttpSessionState during some of those requests. What I WANT is for all the concurrent requests to execute in parallel for performance reasons. What I get is they are serialized by ASP.NET. I experimented with configuring enableSessionState="ReadOnly", but this breaks our Forms Authentication.
Is there a way to get both session state AND concurrency in one session? Do I need to use a custom SessionState or Provider? Any samples of this out there?
PS I'm not worried about thread safety when accessing the SessionState - I can do that programmatically.
From MSDN (link):
However, if two concurrent requests are made for the same session (by using the same SessionID value), the first request gets exclusive access to the session information. The second request executes only after the first request is finished.
So at least for those AJAX calls that require write access to the Session you are out of luck with the default providers.
Not sure if you could get around this using a custom provider.
You can achieve parallell execution for those AJAX calls that do not need access to the session by blocking the ASP.NET_SessionId cookie in an HttpModule. See my answer to this question.
Edit: In order to make this answer more self reliant, I add a slightly modified version of the HttpModule and a bit of discussion (further down). Here's the module code that you can use to prevent Session state from serializing your Ajax calls:
using System;
using System.Web;
namespace TestModule
{
public class TestPreventCookie : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication application)
{
application.BeginRequest +=
(new EventHandler(this.Application_BeginRequest));
application.PostAcquireRequestState +=
(new EventHandler(this.Application_PostAcquireRequestState));
}
private void Application_BeginRequest(Object source, EventArgs e)
{
//prevent session cookie from reaching the service
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
if (BlockCookie(context))
{
context.Request.Cookies.Remove("ASP.NET_SessionId");
}
}
private void Application_PostAcquireRequestState(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
if (BlockCookie(context))
{
var s = context.Session;
if (s != null)
s.Abandon();
}
}
private bool BlockCookie(HttpContext context)
{
// put code here that determines if the session cookie should be blocked
// this could be based on the path of the current request for instance
// only block the cookie when you *know* that access to the Session is not needed
}
}
}
The idea behind this module is that using some criteria based on the project requirements, we remove the ASP.NET_SessionId cookie from the current context (note, we don't expire it on the client).
This means that further on in the request pipeline, the server will create a new session. In order to prevent this newly created session from destroying the existing ASP.NET_SessionId cookie on the client, we abandon it immediately after it has been created.
The end result is that each request that is "intercepted" by the module will execute as if it had no session.

HttpModule is breaking PostBack events

I'm trying to setup a simple HttpModule to handle authentication between my single sign on server. I've included code for the module below. The module is hitting my SSO and properly authenticating; however, on pages with forms the postback events are not occurring properly (e.g. isPostBack value is always false even though a POST occurred, button click events don't get hit, etc.).
public sealed class MyAuthenticationModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.AuthenticateRequest += OnAuthenticateRequest;
}
public void Dispose()
{
}
public static void OnAuthenticateRequest(object sender, EventArgs e)
{
FormsAuthentication.Initialize();
HttpContext context = HttpContext.Current;
HttpRequest request = context.Request;
HttpResponse response = context.Response;
// Validate the ticket coming back from the authentication server
if (!string.IsNullOrEmpty(request["ticket"]))
{
// I can include code for this if you want, but it appears to be
// working correct as whenever I get a ticket from my SSO it is processed
// correctly. I only get a ticket after coming from the SSO server and
// then it is removed from the URL so this only gets hit once.
MyAuthentication.ProcessTicketValidation();
}
if (!request.IsAuthenticated)
{
// redirect to the login server
response.Redirect("https://sso.example.com/login.aspx" + "?" + "service=" +
HttpUtility.UrlEncode(context.Request.Url.AbsoluteUri), false);
}
}
}
EDIT
I would also like to note that if I change the line:
if (!string.IsNullOrEmpty(request["ticket"]))
to:
if (!string.IsNullOrEmpty(request.QueryString["ticket"]))
the problem goes away.
Is it possible that your postbacks have a duplicate form data variable, "ticket"? That would seem to explain the behavior to me.
Aside from that, this line is suspicous:
FormsAuthentication.Initialize();
The FormsAuthentication class uses the "Provider" pattern, which means it's a singleton. You should not re-initialize. From the msdn documentation:
The Initialize method is called when the FormsAuthenticationModule
creates an instance of the FormsAuthentication class. This method is
not intended to be called from your code.

Session variable getting lost?

Given this Global.asax.cs:
using System;
using System.Web;
namespace Foo.Web {
public class Global : HttpApplication {
private const string IntroductionPageShownSessionKey = "IntroductionPageShownSessionKey";
protected void Application_AcquireRequestState(object sender, EventArgs e) {
ShowIntroductionIfNotYetShown();
}
private void ShowIntroductionIfNotYetShown() {
if (HttpContext.Current.Session != null) {
var introductionPageShown = Convert.ToBoolean(Session[IntroductionPageShownSessionKey]);
if (!introductionPageShown) {
if (Request.Path.EndsWith("/Introduction.aspx")) {
Session[IntroductionPageShownSessionKey] = true;
}
else {
Response.Redirect("~/Introduction.aspx" + Request.Url.Query);
}
}
}
}
}
}
User hits webapp and is shown Introduction.aspx
User continues using webapp for a few minutes (ASP.NET_SessionId: ublbhu45ji31e055ywqu0555)
User falls idle (doesn't perform any postbacks) for a few minutes
User performs postback
User is shown Introduction.aspx
Second inspection of user's ASP.NET_SessionId cookie still shows ublbhu45ji31e055ywqu0555
Why is the user shown Introduction.apsx the second time inside the same ASP.NET Session? I'm familiar w/ the risk in setting session variables just before a redirect in the same postback, but that doesn't apply here, right?
Keep in mind that the Session itself likely has a shorter lifetime than the session cookie being sent to the browser and the ID value set in that cookie. In fact, the browser can continue to submit an old session ID, and the server will accept it and create a new session from it, if the old was expired.
The implications being two possibilities:
1) The session is timing out due to the timeout config value (I know not the case in your particular instance)
2) What we've figured out since in your case via the comments to this question: the AppDomain was shutting down or being recycled.

Redirecting users from edit page back to calling page

I am working on a project management web application. The user has a variety of ways to display a list of tasks. When viewing a list page, they click on task and are redirected to the task edit page.
Since they are coming from a variety of ways, I am just curious as to the best way to redirect the user back to the calling page. I have some ideas, but would like to get other developers input.
Would you store the calling url in session? as a cookie? I like the concept of using an object handle the redirection.
I would store the referring URL using the ViewState. Storing this outside the scope of the page (i.e. in the Session state or cookie) may cause problems if more than one browser window is open.
The example below validates that the page was called internally (i.e. not requested directly) and bounces back to the referring page after the user submits their response.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.UrlReferrer == null)
{
//Handle the case where the page is requested directly
throw new Exception("This page has been called without a referring page");
}
if (!IsPostBack)
{
ReturnUrl = Request.UrlReferrer.PathAndQuery;
}
}
public string ReturnUrl
{
get { return ViewState["returnUrl"].ToString(); }
set { ViewState["returnUrl"] = value; }
}
protected void btn_Click(object sender, EventArgs e)
{
//Do what you need to do to save the page
//...
//Go back to calling page
Response.Redirect(ReturnUrl, true);
}
}
This message my be tagged asp.net but I think it is a platform independent issue that pains all new web developers as they seek a 'clean' way to do this.
I think the two options in achieving this are:
A param in the url
A url stored in the session
I don't like the url method, it is a bit messy, and you have to remember to include the param in every relevent URL.
I'd just use an object with static methods for this. The object would wrap around the session item you use to store redirect URLS.
The methods would probably be as follows (all public static):
setRedirectUrl(string URL)
doRedirect(string defaultURL)
setRedirectUrl would be called in any action that produces links / forms which need to redirect to a given url. So say you had a projects view action that generates a list of projects, each with tasks that can be performed on them (e.g. delete, edit) you would call RedirectClass.setRedirectUrl("/project/view-all") in the code for this action.
Then lets say the user clicks delete, they need to be redirected to the view page after a delete action, so in the delete action you would call RedirectClass.setRedirectUrl("/project/view-all"). This method would look to see if the redirect variable was set in the session. If so redirect to that URL. If not, redirect to the default url (the string passed to the setRedirectUrl method).
I agree with "rmbarnes.myopenid.com" regarding this issue as being platform independent.
I would store the calling page URL in the QueryString or in a hidden field (for example in ViewState for ASP.NET). If you will store it outside of the page scope (such as Session, global variable - Application State and so on) then it will not be just overkill as Tom said but it will bring you trouble.
What kind of trouble? Trouble if the user has more than one tab (window) of that browser open. The tabs (or windows) of the same browser will probably share the same session and the redirection will not be the one expected and all the user will feel is that it is a bug.
My 2 eurocents..
I personally would store the required redirection info in an object and handle globally. I would avoid using a QueryString param or the like since they could try bouncing themselves back to a page they are not supposed to (possible security issue?). You could then create a static method to handle the redirection object, which could read the information and act accordingly. This encapsulates your redirection process within one page.
Using an object also means you can later extend it if required (such as adding return messages and other info).
For example (this is a 2 minute rough guideline BTW!):
public partial class _Default : System.Web.UI.Page
{
void Redirect(string url, string messsage)
{
RedirectionParams paras = new RedirectionParams(url, messsage);
RedirectionHandler(paras); // pass to some global method (or this could BE the global method)
}
protected void Button1_Click(object sender, EventArgs e)
{
Redirect("mypage.aspx", "you have been redirected");
}
}
public class RedirectionParams
{
private string _url;
public string URL
{
get { return _url; }
set { _url = value; }
}
private string _message;
public string Message
{
get { return _message; }
set { _message = value; }
}
public RedirectionParams(string url, string message)
{
this.URL = url;
this.Message = message;
}
}

Resources