Tuesday, April 18, 2006

Browsers have been around for a few years now, its bad that some things that most developers need are still so difficult/kludgy. Why is still a pain to tell the difference between the user closing the browser and using the back/forward history buttons ? Yes I recently tangled with OnBeforeUnLoad.

Microsoft ( rightly ) get castigated because IE is not the worlds most CSS complient browser. But in this case the problem is the standards dont seem to address a common and simple development need.

When somebody logs on to our software a row gets created in a SQL table. They logout and the row is deleted. If they are already logged on they cant login again as the same user. Instead an administrator needs to go in and unlock them. This is part of our licencing system. A issue comes in the ASP.NET version of one of our applications, the user can just close the browser instead of clicking logout. So we needed to make the software logout the user however they exit the browser. ( Short of killing the task in task manager ). My manager, not unsensibly, expected this to be a trivial thing to code. Well its not rocket science but theres way too many steps here.

First port of call was the JavaScript event OnBeforeUnload. I google that attaching code to here isnt foolproof and it must run fast, otherwise it wont get to finish running. So decide to go for an asynchronous AJAX type call.

I have used AJAX.NET before but this project hasnt got any more needs for AJAX calls at present, downloading the latest version of AJAX.NET shows that more changes to web.config are needed over the earlier version I had used before. Seemed silly to do all that and add another DLL just for this.

So back to basics a JavaScript include file with a function which manually creates the XMLHttpRequest object and calls a webpage, signout.aspx. Create the new webpage, edit its page_load method to retrieve the current userID from the session state and use SQLConnection, SQLCommand objects to do the deletion.

Added this javascript call in the OnBeforeUnload event of a body tag in the default.aspx page to test. Works great. Erm hang on a minute it also unlocks the user if they click on a link on the page, or use back, or use forwards. Oops. Another google shows that yes this really is the best event to use.

OK I can fix the logout when user clicks on a link by attaching some JavaScript to the body tags OnClick event. It sets a global ( yeuch ) variable to true which tells my main JavaScript function to ignore the next logout request.

OK that works but what backspace/forwardspace ? ( What about that link which renders a Crystal Report as PDF, this page isnt even HTML its pure PDF, so no JavaScript can be attached )

OK I could handle keypresses, look for backspace or forward keys and set the global ignore logout request variable. This gets worse. And it still doesnt help if user presses the backspace button.

Change of thinking came with the realisation that I cannot have my logout code fire only when the user closes the browser window ( Installing browser addons isnt an option because its horrible and because our application users might be in an internet cafe ).

So instead I decide to live with the fact that the user record will get deleted if the users uses the history feature. To counter this each page must readd the record if its gone. So now the logon process stores all the information it needs to reinsert this record in session state. Each pages page_load method checks for this missing record and readds it if needed. In the extremely unlikely event that the user logged on with the same username, e.g. into the WIN32 application, the browser has to tell them this and prevent further actions until they logout of there.

That just leaves the PDF report page which has no way of calling JavaScript code.
In this case the logon record is purposely deleted as this page is loaded. So if they exit the browser when displaying the report all is OK, they are already logged out. If they backspace to the previous page then page_load on this page takes care of putting the record back.

So everything worked out but boy that was a lot of hoops to jump through !

It introduces a maintenance issue in that any new webpages added must make this ( one line ) call to ensure the user record is there. Maybe I could do away with this by adding a descendant of the Page class and having all my pages descend from there, or by adding the logic into the HTTP pipeline elsewhere - but this way is at least easy to understand when reading the code and it works.

No comments: