locked
Scripting events no longer fire when a user leaves site or closes browser even though app is still running. RRS feed

  • Question

  • User-1335950151 posted

    A project I am working with implements three JAVA script "timers" from a Master Page to track the session time out and to do a "heartbeat" check. The "setTimerout" timer redirects the user to a warning page if the time out count reaches 1 and a half minutes before a session time out will occur. The two "setInterval" timers are implemented to track if the user's browser is sill active on the site. Following are several code snippets that implement the "timers" and the "Heartbeat" and "Heartbeat Check"  handlers.

    Master Page code to activate timers:

    Dim BaseAccountTimeout As Single = CType((CType(CurrentSessionTimeout, Single) - 1.5), Single)
    If Session.Item("UseSessionTimeout") Then
        Dim TimeoutAcount As String = CType(((BaseAccountTimeout * 60) * 1000), Integer).ToString
        Page.ClientScript.RegisterStartupScript(Me.GetType, "TimeoutScript", "setTimeout(""top.location.href = '" & GotoTimeoutPage & "'""," & TimeoutAcount & ");", True)
    End If
    If Session.Item("UseHeartBeat") Then
        Dim BaseHeartBeatTimeout As Single = CType(CurrentHeartBeatTimeout, Single)
        Dim BaseHeartBeatCheckTimeout As Single = CType((BaseHeartBeatTimeout + 1.5), Single)
        Dim TimeoutHeartBeat As String = CType(((BaseHeartBeatTimeout * 60) * 1000), Integer).ToString
        Dim TimeoutHeartBeatCheck As String = CType(((BaseHeartBeatCheckTimeout * 60) * 1000), Integer).ToString
        Page.ClientScript.RegisterStartupScript(Me.GetType, "HeartBeatScript", "setInterval(""ajax.post('/pages/heartbeat.ashx', {id: '" + Session.SessionID + "'}, function() {})""," & TimeoutHeartBeat & ");", True)
        Page.ClientScript.RegisterStartupScript(Me.GetType, "HeartBeatCheckScript", "setInterval(""ajax.post('/pages/heartbeatcheck.ashx', {id: '" + Session.SessionID + "'}, function() {})""," & TimeoutHeartBeatCheck & ");", True)
    End If

    Heartbeat handler code:

    <%@ webhandler language="VB" class="HeartbeatHandler" %>
    Imports System
    Imports System.Web
    Imports System.Data
    Imports System.Data.SqlClient
    Public Class HeartbeatHandler
        Implements IHttpHandler
        Public ReadOnly Property IsReusable As Boolean Implements IHttpHandler.IsReusable
            Get
                Return True
            End Get
        End Property
        Public Sub ProcessRequest(ByVal ctx As HttpContext) Implements IHttpHandler.ProcessRequest
            Dim sqlConnection1 As New System.Data.SqlClient.SqlConnection(ConfigurationManager.ConnectionStrings("ApplicationServices").ConnectionString)
            Dim cmd As New System.Data.SqlClient.SqlCommand
            Dim dreader As System.Data.SqlClient.SqlDataReader
            Dim GotOne As Boolean = False
            Dim ValuesString As String = ""
            Dim FieldsString As String = ""
            Dim id As String = ""
            id = ctx.Request.Params.Item("id")
            If IsNothing(id) Then id = ""
            If id = "" Then GoTo EXITHEARTBEAT
            cmd.Connection = sqlConnection1
            cmd.CommandType = System.Data.CommandType.Text
            If cmd.Parameters.Count = 0 Then
                cmd.Parameters.Add("@uID", System.Data.SqlDbType.NVarChar)
                cmd.Parameters.Add("@Date", System.Data.SqlDbType.DateTime)
                cmd.Parameters.Add("@IP", System.Data.SqlDbType.NVarChar)
            End If
            cmd.Parameters("@uID").Value = id
            cmd.Parameters("@Date").Value = CType(Now, DateTime)
            cmd.Parameters("@IP").Value = HttpContext.Current.Request.UserHostAddress
            Try
                cmd.CommandText = "SELECT SessionID FROM aspnet_Sessions WHERE SessionID = @uID"
                sqlConnection1.Open()
                dreader = cmd.ExecuteReader()
                If dreader.Read() Then GotOne = True
                dreader.Close()
            Catch ex As Exception
                ' do nothing here
            Finally
                sqlConnection1.Close()
            End Try
            If GotOne Then
                Try
                    ValuesString = "LastRequest = @Date"
                    cmd.CommandText = "UPDATE aspnet_Sessions SET " + ValuesString + " WHERE SessionID = @uID"
                    sqlConnection1.Open()
                    cmd.ExecuteNonQuery()
                Catch ex As Exception
                    ' do nothing here
                Finally
                    sqlConnection1.Close()
                End Try
            Else
                Try
                    FieldsString = "(Record, LiveDate, SessionID, LastRequest, IPAddress)"
                    ValuesString = "(NEWID(), @Date, @uID, @Date, @IP)"
                    cmd.CommandText = "INSERT aspnet_Sessions " + FieldsString + " VALUES " + ValuesString
                    sqlConnection1.Open()
                    cmd.ExecuteNonQuery()
                Catch ex As Exception
                    ' do nothing here
                Finally
                    sqlConnection1.Close()
                End Try
            End If
    EXITHEARTBEAT:
        End Sub
    End Class

    Heartbeat check handler code:

    <%@ webhandler language="VB" class="HeartbeatCheckHandler" %>
    Imports System
    Imports System.Web
    Imports System.Data
    Imports System.Data.SqlClient
    Public Class HeartbeatCheckHandler
        Implements IHttpHandler
        Public ReadOnly Property IsReusable As Boolean Implements IHttpHandler.IsReusable
            Get
                Return True
            End Get
        End Property
        Public Sub ProcessRequest(ByVal ctx As HttpContext) Implements IHttpHandler.ProcessRequest
            Dim sqlConnection1 As New System.Data.SqlClient.SqlConnection(ConfigurationManager.ConnectionStrings("ApplicationServices").ConnectionString)
            Dim cmd As New System.Data.SqlClient.SqlCommand
            Dim dreader As System.Data.SqlClient.SqlDataReader
            Dim GotOne As Boolean = Nothing
            Dim ValuesString As String = ""
            Dim FieldsString As String = ""
            Dim LastRequest As DateTime = Nothing
            Dim TimeDifference As Long = 0
            Dim HeartBeatTimeout As Single = CommonCode.Common.DefaultHeartBeatTimeout
            Dim MissedHeartBeats As Single = CommonCode.Common.DefaultMissedHeartBeats
            Dim id As String = ""
            id = ctx.Request.Params.Item("id")
            If IsNothing(id) Then id = ""
            If id = "" Then GoTo EXITHEARTBEATCHECK
            cmd.Connection = sqlConnection1
            cmd.CommandType = System.Data.CommandType.Text
            Try
                cmd.CommandText = "SELECT HeartBeatTimeout, MaximumHeartBeats FROM aspnet_WebSiteSettings"
                cmd.CommandType = System.Data.CommandType.Text
                sqlConnection1.Open()
                dreader = cmd.ExecuteReader()
                If dreader.Read() Then
                    HeartBeatTimeout = dreader("HeartBeatTimeout")
                    MissedHeartBeats = dreader("MaximumHeartBeats")
                End If
                dreader.Close()
            Catch ex As Exception
                ' do nothing here
            Finally
                sqlConnection1.Close()
            End Try
            Dim MaximumDifference As Single = (HeartBeatTimeout * MissedHeartBeats) + 0.5
            If cmd.Parameters.Count = 0 Then cmd.Parameters.Add("@uID", System.Data.SqlDbType.NVarChar)
            cmd.Parameters("@uID").Value = id
            Try
                cmd.CommandText = "SELECT SessionID, LastRequest FROM aspnet_Sessions WHERE SessionID = @uID"
                cmd.CommandType = System.Data.CommandType.Text
                sqlConnection1.Open()
                dreader = cmd.ExecuteReader()
                If dreader.Read() Then
                    If Not IsDBNull(dreader("LastRequest")) Then
                        If dreader("LastRequest").ToString.Trim <> "" Then
                            LastRequest = dreader("LastRequest")
                            TimeDifference = DateDiff(DateInterval.Minute, LastRequest, CType(Now, DateTime))
                            If TimeDifference > MaximumDifference Then GotOne = True
                        Else
                            GotOne = False
                        End If
                    Else
                        GotOne = False
                    End If
                Else
                    GotOne = False
                End If
                dreader.Close()
            Catch ex As Exception
                ' do nothing here
            Finally
                sqlConnection1.Close()
            End Try
            If GotOne Then
                Try
                    cmd.CommandText = "DELETE aspnet_Sessions WHERE SessionID = @uID"
                    cmd.CommandType = System.Data.CommandType.Text
                    sqlConnection1.Open()
                    cmd.ExecuteNonQuery()
                Catch ex As Exception
                    ' do nothing here
                Finally
                    sqlConnection1.Close()
                End Try
                CommonCode.Common.DeleteFromSessionIDCollection(id)
                CommonCode.Common.SessionData(CommonCode.Common.DeleteSessionData, id)
                CommonCode.Common.DisposeSession()
            End If
    EXITHEARTBEATCHECK:
        End Sub
    End Class

    Code on Master Page:

            var ajax = {};
            ajax.x = function () {
                try {
                    return new ActiveXObject('Msxml2.XMLHTTP')
                } catch (e1) {
                    try {
                        return new ActiveXObject('Microsoft.XMLHTTP')
                    } catch (e2) {
                        return new XMLHttpRequest()
                    }
                }
            };
            ajax.send = function (url, callback, method, data, sync) {
                var x = ajax.x();
                x.open(method, url, sync);
                x.onreadystatechange = function () {
                    if (x.readyState == 4) {
                        callback(x.responseText)
                    }
                };
                if (method == 'POST') {
                    x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                }
                x.send(data)
            };
            ajax.get = function (url, data, callback, sync) {
                var query = [];
                for (var key in data) {
                    query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
                }
                ajax.send(url + '?' + query.join('&'), callback, 'GET', null, sync)
            };
            ajax.post = function (url, data, callback, sync) {
                var query = [];
                for (var key in data) {
                    query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
                }
                ajax.send(url, callback, 'POST', query.join('&'), sync)
            };
    

    I have discovered that the JAVA script timers only continue to fire while a browser is on the site. As soon as the user closes the browser or navigates away from the site, even though the web application is still running, the only event that files is the ASP.NET "Session_End" event in Global.asax when the session times out. None of the "timers" will fire which I am assuming is caused by the fact that the Master Page is no longer loaded so the scripts on the Master do not fire.

    I don't really care about the "setTimeout" timer, since there is no need to redirect to a timeout page if the browser is no longer active on the site, so that script can stay on the Master page. However, the two "setInterval" timers are what I am trying to keep running so the app can detect if the user has left the site and then the app can remove the session data and temp directories for that session.

    My question is this: Does anyone know of a way to run these two scripts from a service or some other way to implement them so they will continue to fire even though the browser is closed or has gone to another site?

    Thanks!
    David

    Tuesday, December 24, 2013 11:01 AM

Answers

  • User753101303 posted

    Hi,

    No, it runs client side so by definition if the user goes to another page or closes his browser, your content is unloaded and doesn't run any more. Keep in mind that the "web app" is server side (and doesn't even really "run" it just render pages as requested and only "on demand". You could keep Session_End and possibly use a safe guard measure such as removing a directory if its too old (or even don't use at all session bound directories, not sure about their usage).

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, December 26, 2013 3:12 PM

All replies

  • User753101303 posted

    Hi,

    No, it runs client side so by definition if the user goes to another page or closes his browser, your content is unloaded and doesn't run any more. Keep in mind that the "web app" is server side (and doesn't even really "run" it just render pages as requested and only "on demand". You could keep Session_End and possibly use a safe guard measure such as removing a directory if its too old (or even don't use at all session bound directories, not sure about their usage).

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, December 26, 2013 3:12 PM
  • User-1335950151 posted

    Thanks for your reply. I have come to the conclusion that there isn't a way to fire an event when a browser is not on the site. It appears that as 'cool' as the heart beat idea might be, there is no practical way to make it work from an ASP.NET application web site that uses code behind with page generated Javascripts. Hopefully this post will be helpful to other who might be considering this and keep them from wasting their time ;)

    Currently the application's routines are being reworked to use the Session_End event instead. When the Session_End event fires, the session data is eventually gone, so you cannot use session variables for any cleanup routines. Instead the plan is to create some global App_Code variables that will be set in the Application_Start event and then use them in the Session_End event to remove any inactive session data and temp directories.

    Thursday, December 26, 2013 5:35 PM