Sunday, July 24, 2011

ASP.NET Session Timeout Control with jQuery UI Dialog

As you may or may not be aware, back in 2009 I posted code where I had extended an ASP.NET session timeout control originally created by Travis Collins. That control then later evolved into a second version that included a countdown timer, and later still I made it .NET 4.0 compatible. All along the way I attempted to retain as much of the original code as possible, supporting the original shown/hidden div approach Travis created - as well as my jQuery UI dialog approach. While this seemed like a good idea at the time, it ultimately led to some ugly code as well as a bit of code bloat for no good reason - and I've finally gotten around to doing something about it.

I've rewritten most of the code now to such a degree that it looks nothing like the original control from Travis. The javascript class especially has been completely reworked, notably forgoing prototypes for simple closures. I did this largely for two reasons. The first is that there's no reason to have more than one Timeout class instantiated on a page. The second is that I hope the closure style is simpler for everyone to follow.

Please note that due to all these changes, I have renamed the project, namespace, etc. - dropping all references to the original TSC name - as well as reset the version number to 1.0.

This new version functionally is very similar to the last, but jQuery is now a requirement. Also partial postbacks are no longer optional - they will simply always reset the control just as a full postback would. The countdown timer is also no longer optional. It is simply required. If you don't want to show it, simply place a display hidden style on the span.

The demo project has been updated to use the latest version of jQuery and the jQuery UI (installed as NuGet packages).

There is one new bit of functionality to take note of. You can now reset the timeout from outside the control (something requested a few times over the years). Simply grab a reference and call the reset() method using $find like this:
$find('myTimeout1').reset();

Also to foster social development, I have moved the code to a github repository so fork away!

30 comments:

  1. Very useful one. Thanks for still updating..

    ReplyDelete
  2. Hey this is great. I like the bility to reset the timer from outside the control. I have project where I will need to reset the timer from a child window. However the project is using ASP.NET 3.5 so I am not sure if it will work or not.

    Sorry if this is a multiple post but google is someone not validating my account.

    --John Maddox

    ReplyDelete
  3. John-
    There's really no true dependency on 4.0. You should be able to drop it back without any code changes.

    BTW, just as FYI, I will be updating the github master this weekend with a fairly large update. I've reworked the code a bit (actually had to go back to a little bit of a mixture of prototypes + closures) but it will now work with any type of window/dialog/popup etc you want to use very easily. It still uses the jquery UI by default, but it'll be easier to use with other control libraries.

    ReplyDelete
  4. I got this error
    Warning 16 D:\XXXX\MasterPages\AMAP.master: ASP.NET runtime error: Could not load file or assembly 'AjaxControls' or one of its dependencies. This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.

    ReplyDelete
  5. John-
    Yes, you'll need to drop the solution back to .NET 3.5 before you'll be able to use it in a 3.5 web app.

    ReplyDelete
  6. Thanks. I did that. I took the source code and recompiled as a .NET 3.5 Testing solution now (not your code I know it is good but my code)

    ReplyDelete
  7. FYI-
    I've just updated the github master to the latest version which includes changes that should now support the control's use with virtually any kind of popup/modal/element/etc. Public methods are available for initDialog, closeDialog, openDialog, and reset - so you can literally open/close/show/hide whatever you want - and the public reset method allows the control to be reset from anywhere on the page. It still uses the jQueryUI by default, but I've also thrown in examples for SimpleModal and ThickBox - and plan to add more later.

    ReplyDelete
  8. I am currently using teh timeout control to have a popup send a message back to a parent window so the parent window can reset the timer. Is there a way to do this other than
    $find('ctl00_Timeout1').reset();
    The problem is that the timer control is on a master page and I did not want to hardcode the name becasue the master page mangling may change. I have tried the jQuery regular expression '[id$=_Timeout1]' but it does not return teh same thing.

    ReplyDelete
  9. jamaddox-

    The more proper solution would be to expose the clientid of the timeout control - similar to how I'm exposing the Enabled property (if you look at the code-behind for the master pages in the download).

    For reference:
    http://msdn.microsoft.com/en-us/library/c8y19k6h.aspx

    Then you can access it on your content pages programmatically or in your case probably via server tags in the html: <%= TimeoutClientId %> - or whatever you call the property.

    That's actually the very reason I exposed the Enabled property in the demo - so you could access it (and manipulate it) from content pages easily.

    If you're looking for an easier solution, I think you can do it with jquery like you mentioned - but I think you'd need to strip off the jQuery wrapper with [0] so you'd get the actual DOM element:
    $('[id$=_Timeout1]')[0]

    (but I haven't tested that)

    Good luck-
    Kenneth

    ReplyDelete
  10. jamaddox-

    P.S.
    I'll try to update the demo project to include an
    example of exposing and referencing some properties in the next couple of days-

    Kenneth

    ReplyDelete
  11. jamaddox-

    I've updated the github master - the Div demo now shows how you can access the control's clientid from a content page (and reset the control via MS Ajax or jQuery either one).

    let me know if you still have questions-

    ReplyDelete
  12. Thanks for your help. I was nto complete in my post. What I as trying to do was to not modify any of the content pages but add the reset command to a script file on the master. I ultimate added a function to my master scripts that did this
    var tc = $('[id$=Timeout1]');
    $find(tc[1].id).reset();

    My earlier problem was in not knowing that jquery returned something different than find was expecting for input. Your examples and discussions gave me some valuable insight.

    The examples did work but with a large number of pages I just did not want to modify every page in the website.

    Thanks

    ReplyDelete
  13. How do you recommend getting around the sliding expiration problem? i.e. session on server is only renewed if half of the time has passed. My problem (with forms authentication and 10 minute timeout):

    User logs in at 10:00. Both server timeout and client timeout are set to 10:10. Reset() is called on the the client at 10:04. This resets the client timeout to 10:14, but since less than half of the timeout time has elapsed, the server does not reset the session; it stays at 10:10. So now the client timeout is 10:14, but the server times out at 10:10. So at 10:13 the user gets the popup (because client does not know that session has already timed out), clicks "Reset Timeout" and thinks all is well. But then tries to navigate to a new page and gets redirected to the login page instead!

    I need a way to force the server to reset the authentication cookie expiration when your timeout control does it's AJAX callback, REGARDLESS of how much time has passed. Any ideas?

    ReplyDelete
  14. Kent-

    Perhaps you could add code in the Timeout.cs file's RaiseCallbackEvent method to accomplish what you're after.

    Check it out and let me know.

    Thanks-
    Kenneth

    ReplyDelete
  15. Great suggestion Kenneth! This surely must be the de-facto session timeout control for ASP.Net! Here is how I modified your code to reset the forms authentication ticket, along with the session reset:

    public void RaiseCallbackEvent(String eventArgument)
    {
    // Session gets updated automatically

    // Manually Update the authentication ticket to overcome ASP.Net quirk
    if (HttpContext.Current.Request.IsAuthenticated)
    {
    // Acquire the old ticket
    FormsAuthenticationTicket oldTicket = ((FormsIdentity)Context.User.Identity).Ticket;

    // Create auth cookie & related ticket
    HttpCookie authCookie = FormsAuthentication.GetAuthCookie(oldTicket.Name, true);
    FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);

    // Manually slide the expiration
    FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration,
    ticket.IsPersistent, oldTicket.UserData);

    if (newTicket.Expiration > oldTicket.Expiration)
    {
    // Create the (encrypted) cookie.
    authCookie.Value = FormsAuthentication.Encrypt(newTicket);

    // Add the cookie to the list for outbound response.
    HttpContext.Current.Response.Cookies.Add(authCookie);
    }
    }
    }

    ReplyDelete
  16. Kent-
    Could you email me at: mr(dot)kenneth(dot)scott(at)gmail.com ?

    I'm very intrigued by your solution to the sliding expiration issue and I'd like to discuss it with you if you have a minute.

    Thanks-
    Kenneth

    ReplyDelete
  17. Got an error : "Sys.InvalidOperationException: Two components with the same id '[....]' can't be added to the application."
    Whats problem occured with the timeout control.

    ReplyDelete
  18. Anonymous-

    Do you have another control on the page with the same ID as the timeout control?

    Kenneth

    ReplyDelete
  19. I am having a master page with the timeout control and some content pages that all have a common baseclass that implements ICallbackEventHandler - and there we have the problem.

    Timeout.cs implements ICallbackEventHandler. So GetCallbackResult is called on timeout.cs instead of my basepage.

    Any suggestions on how to solve that problem?

    ReplyDelete
  20. Martin-

    Interesting dilemma. Perhaps I could expose and raise the event so that you could subscribe to it on the page. I'll have to do a little research on that one. Do you think that would help in your scenario?

    Kenneth

    ReplyDelete
  21. Hello Kenneth,

    I just have implemented the Session Timeout Control. However I am struggling with the message: "Error: jQuery not found!".

    I suppose it has something to do with the jQuery.min.js script on the MasterPage. However I copied everything literary from the Demo solution you provided.

    What am I forgetting here?

    ReplyDelete
  22. Hello Kenneth,

    In addition to my previous post. It seems like the Session Timeout control is interfering with the AJAXControlToolkit.

    Can this be possible?

    ReplyDelete
  23. Teknologi-

    You must have jQuery loaded for the control to function properly. See the script tag on the master page.

    Regarding the AjaxControlToolkit - I am not sure. I've never used the timeout control with it so I'd have to try it. I don't know of any reason they'd conflict though. Which control in the toolkit specifically do you think it's conflicting with?

    ReplyDelete
  24. Kenneth -

    The AjaxControlToolkit controls which do not work anymore after loading jQuery are (as far as I noticed): UpdateProgressPanel, ModularPopupExtender.

    ReplyDelete
  25. Teknologic-

    I was unable to reproduce any errors when the Timeout control was used in combination with the AJAX Control Toolkit. Are you sure you have everything setup properly?

    The AJAX Control Toolkit has a script manager control of its own, but I just used the standard .NET script manager control (which is the same one used by the Timeout control) and I didn't notice any problems. The main thing to remember is that you can't use 2 script managers at the same time.

    The only minor thing I saw was that you'll have to adjust the z-index property of either the ModalPopup or the jQuery UI modal's css. I believe the ModalPopup's z-index setting is higher than that of the jQuery UI modal - so if the ModalPopup is visible, the timeout control popup shows underneath. But that's simply a style issue.

    If you provide more details maybe I can give you more info-

    ReplyDelete
  26. @Kenneth:

    It seems that my masterpage doesn't seem to load the jQuery library, Nevertheless the script-include-tag in het html head section:

    head>
    script src="Scripts/jquery-1.7.min.js" type="text/Javascript">/script>
    /head>

    That's what the message: "Error: jQuery not found!" actually is saying. After that I directly get another JScript error in Visual Studio stating:

    "Runtime-error Microsoft JScript: $ is not defined.".

    Which again is pointing out that the JQuery library is not loaded.

    ReplyDelete
  27. Teknologi-
    It just occurred to me - your problem is most likely that your page isn't in the same (root) location as your master page. Therefore the relative path reference in the script tag won't work. You'll have to switch to an absolute path reference or perhaps a protocol relative reference (or you could just use a CDN).

    ReplyDelete
  28. @Kenneth:

    Yes! Using a CDN to the jQuery library is doing the trick!

    I tried changing the relative path to the jQuery library, however this did not work for my project. Now I realize why.

    Because I have some pages located in the root of my project and some located in subfolders, one or the other couldn't load the jQuery library because the relative path was one folder off. A CDN fixed it.

    Thank you Kenneth for your time and patience!

    ReplyDelete
  29. Hello, I am having the same issue that the last post fixed. Can you provide an example of a CDN? (syntax)? Thank you

    ReplyDelete
  30. An example of a CDN could be http://sub.domain.com

    ReplyDelete