在asp.net中会话超时时通知用户

时间:2010-02-10 04:09:34

标签: asp.net session

会话过期后向用户显示消息的最佳方法是什么? 到那时,用户应该注销并重定向到开始页面。 我可以使用javascript将用户重定向到起始页面。我只是想在首页上显示一条消息。

4 个答案:

答案 0 :(得分:15)

此答案的先前版本包含一些简单的来源,可以解决不再存在的问题。

情节是他希望在 会话超时前通知 ,以便采取行动。

目前尚不清楚它是asp.net会话还是表单身份验证票证,但实际上是同一个问题。

基本上相当于异步会话状态管理而不会触发会话/票证续订。 ajax应用程序的共同愿望。

解决方案非常简单,但并不完全直截了当。有很多问题需要处理,但我匆匆解决了一个对于简单的会话状态和表单身份验证都可靠的解决方案。

它由一个非常轻的httpmodule和随附的客户端脚本库组成,不超过50或60行有效代码。

策略是提供一个http端点,可以查询会话的到期状态或表单票证而无需续订。该模块只是位于管道的前端,并使用javascript日期和一个非常简单的js lib来为几个“虚拟”端点提供服务以进行调用。

演示和来源位于:AsynchronousSessionAuditor

你们当中有些人想知道为什么我会关心这一点,但可能你们当中有更多人在说地狱是的,这是一个痛苦的事情。我知道我过去曾经提出过脆弱的解决方法,我很满意这个。

它可以在标准的回发模型应用程序中运行,或者更可能在ajax应用程序中运行。 也... 您可以详细了解Http请求生命周期herehere。如果有兴趣,请了解有关在asp.net端点here

中使用原始xmlhttp的更多信息

删除之前的加标代码。如果您有兴趣,请查看修订历史记录。

以下是相关的源文件。可从上面提供的链接获得完整的参考实现....

<强> HTTP模块

// <copyright project="AsynchronousSessionAuditor" file="AsynchronousSessionAuditorModule.cs" company="Sky Sanders">
// This source is a Public Domain Dedication. 
// http://spikes.codeplex.com
// Attribution is appreciated.
// </copyright> 

using System;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;

namespace Salient.Web.Security
{
    /// <summary>
    /// AsynchronousSessionAuditorModule provides a mechanism for auditing the lifecycle
    /// of an ASP.Net session and/or an ASP.Net FormsAuthentication cookie in the interest
    /// of providing better monitoring and control of a user session. This is becoming more
    /// important as more and more web apps are single page apps that may not cycle a page for
    /// many minutes or hours.
    /// 
    /// Storing a token in the Asp.net Session was for many years the default authentication strategy.
    /// The are still valid applications for this techique although FormsAuthentication has far
    /// surpassed in security and functionality.
    /// 
    /// What I am providing is a manner in which to transparently monitor the expiration status of each
    /// by implementing a module that recognizes two virtual endpoints:
    /// 
    ///   http://mysite/.aspnetsession
    ///   http://mysite/.formsauthticket
    /// 
    /// By making a request to these urls you will be delivered a javascript date in numeric form that
    /// represents the expiration dateTime of either the current ASP.Net session, if any, or the current
    /// FormsAuthentication ticket expiration, if any.
    /// 
    /// If the requested item does not exists, zero is returned. Any value served by this module should
    /// be cast to a date and compared with Now. If less than you should take action. You should have
    /// taken action on the client before the session timed out, aided by the output of this module, but
    /// hey, nobody is perfect.
    /// </summary>
    public class AsynchronousSessionAuditorModule : IHttpModule
    {
        // note: these must remain in sync with the string keys in the javascript
        private const string AspSessionAuditKey = ".aspnetsession";

        private const string FormsAuthAuditKey = ".formsauthticket";

        #region IHttpModule Members

        public void Init(HttpApplication context)
        {
            // this is our audit hook. get the request before anyone else does
            // and if it is for us handle it and end. no one is the wiser.
            // otherwise just let it pass...
            context.BeginRequest += HandleAuditRequest;

            // this is as early as we can access session. 
            // it is also the latest we can get in, as the script handler is coming
            // right after and we want to beat the script handler to the request
            // will have to set a cookie for the next audit request to read in Begin request.
            // the cookie is used nowhere else.
            context.PostAcquireRequestState += SetAuditBugs;
        }

        public void Dispose()
        {
        }

        #endregion

        private static void SetAuditBugs(object sender, EventArgs e)
        {
            HttpApplication app = (HttpApplication) sender;

            if ((app.Context.Handler is IRequiresSessionState || app.Context.Handler is IReadOnlySessionState))
            {
                HttpCookie sessionTimeoutCookie = new HttpCookie(AspSessionAuditKey);

                // check to see if there is a session cookie
                string cookieHeader = app.Context.Request.Headers["Cookie"];
                if ((null != cookieHeader) && (cookieHeader.IndexOf("ASP.NET_SessionId") >= 0) &&
                    !app.Context.Session.IsNewSession)
                {
                    // session is live and this is a request so lets ensure the life span
                    app.Context.Session["__________SessionKicker"] = DateTime.Now;
                    sessionTimeoutCookie.Expires = DateTime.Now.AddMinutes(app.Session.Timeout).AddSeconds(2);
                    sessionTimeoutCookie.Value = MilliTimeStamp(sessionTimeoutCookie.Expires).ToString();
                }
                else
                {
                    // session has timed out; don't fiddle with it
                    sessionTimeoutCookie.Expires = DateTime.Now.AddDays(-30);
                    sessionTimeoutCookie.Value = 0.ToString();
                }
                app.Response.Cookies.Add(sessionTimeoutCookie);
            }
        }

        private static void HandleAuditRequest(object sender, EventArgs e)
        {
            HttpContext context = ((HttpApplication) sender).Context;
            bool formsAudit = context.Request.Url.PathAndQuery.ToLower().StartsWith("/" + FormsAuthAuditKey);
            bool aspSessionAudit = context.Request.Url.PathAndQuery.ToLower().StartsWith("/" + AspSessionAuditKey);

            if (!formsAudit && !aspSessionAudit)
            {
                // your are not the droids i am looking for, you may move along...
                return;
            }

            double timeout;
            // want to know forms auth status
            if (formsAudit)
            {
                HttpCookie formsAuthCookie = context.Request.Cookies[FormsAuthentication.FormsCookieName];
                if (formsAuthCookie != null)
                {
                    FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(formsAuthCookie.Value);
                    timeout = MilliTimeStamp(ticket.Expiration);
                }
                else
                {
                    timeout = 0;
                }
            }
                // want to know session status
            else
            {
                // no session here, just take the word of SetAuditBugs
                HttpCookie sessionTimeoutCookie = context.Request.Cookies[AspSessionAuditKey];
                timeout = sessionTimeoutCookie == null ? 0 : Convert.ToDouble(sessionTimeoutCookie.Value);
            }

            // ensure that the response is not cached. That would defeat the whole purpose
            context.Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(-1));
            context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            context.Response.Cache.SetNoStore();
            // the money shot. a javascript date.
            context.Response.Write(timeout.ToString());
            context.Response.Flush();
            context.Response.End();
        }

        /// <summary>
        /// Found Code: http://forums.asp.net/t/1044408.aspx
        /// </summary>
        /// <param name="TheDate"></param>
        /// <returns></returns>
        private static double MilliTimeStamp(DateTime TheDate)
        {
            DateTime d1 = new DateTime(1970, 1, 1);
            DateTime d2 = TheDate.ToUniversalTime();
            TimeSpan ts = new TimeSpan(d2.Ticks - d1.Ticks);

            return ts.TotalMilliseconds;
        }
    }
}

客户端库

// <copyright project="AsynchronousSessionAuditor" file="AsynchronousSessionAuditor.js" company="Sky Sanders">
// This source is a Public Domain Dedication. 
// http://spikes.codeplex.com
// Attribution is appreciated.
// </copyright> 


var AsynchronousSessionAuditor = {
    /// this script really should be served as a resource embedded in the assembly of the module
    /// especially to keep the keys syncronized

    pollingInterval: 60000, // 60 second polling. Not horrible, except for the server logs. ;)

    formsAuthAuditKey: ".formsauthticket", // convenience members
    aspSessionAuditKey: ".aspnetsession",

    errorCallback: function(key, xhr) {
        /// <summary>
        /// Default behavior is to redirect to Default and provide the xhr error status text
        /// in the loggedout query param.
        ///
        /// You may replace this default behaviour with your own handler. 
        /// e.g.  AsynchronousSessionAuditor.errorCallback = myMethod;
        /// </summary>
        /// <param name="key" type="String"></param>
        /// <param name="xhr" type="XMLHttpRequest"></param>
        window.location = "Default.aspx?loggedout=Error+" + xhr.statusText;
    },

    timeoutCallback: function(key, xhr) {
        /// <summary>
        /// Default behavior is to redirect to Default and provide the key value
        /// in the loggedout query param.
        ///
        /// You may replace this default behaviour with your own handler.
        /// e.g.  AsynchronousSessionAuditor.timeoutCallback= myMethod;
        /// </summary>
        /// <param name="key" type="String"></param>
        /// <param name="xhr" type="XMLHttpRequest"></param>    
        window.location = "Default.aspx?loggedout=" + key;
        // or just refresh. you will be sent to login.aspx
    },

    statusCallback: function(value) {
        /// <summary>
        /// Default behavior is to do nothing, which is not very interesting.
        /// This value is set when AsynchronousSessionAuditor.init is called
        /// </summary>
        /// <param name="value" type="String">
        /// The responseText of the audit request. Most certainly is a JavaScript Date
        /// as a number. Just cast to date to get the requested expiration dateTime.
        /// e.g. var exp = new Date(parseFloat(value)); if (isNaN(exp)){this should never happen}
        /// </param>

        window.location = "Default.aspx?loggedout=" + key;
        // or just refresh. you will be sent to login.aspx
    },

    createXHR: function() {
        /// <summary>
        /// This xhr factory is not the best I have see.
        /// You may wish to replace it with another or
        /// use your favorite ajax library to make the
        /// call.
        /// </summary>
        var xhr;

        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
        }
        else if (window.ActiveXObject) {
            xhr = new ActiveXObject('Microsoft.XMLHTTP');
        }
        else {
            throw new Error("Could not create XMLHttpRequest object.");
        }
        return xhr;
    },


    auditSession: function(key) {
        /// <summary>
        /// Make a request that will be serviced by the audit module to determine the 
        /// state of the current FormsAuthentication ticket or Asp.Net session 
        ///
        /// The return value is a JavaScript date, in numeric form, that represents the
        /// expiration of the item specified by key.
        /// Just cast it to date, i.e. new Date(parseFloat(xhr.resposeText))
        /// </summary>
        /// <param name="key" type="String">
        /// the server key for the item to audit.
        ///
        /// use ".formsauthticket" to get the expiration dateTime for the forms authentication
        /// ticket, if any.
        ///
        /// use ".aspnetsession" to get the expiration of the current ASP.Net session.
        ///
        /// Both have convenience members on this object.
        /// </param>

        var xhr = AsynchronousSessionAuditor.createXHR();

        xhr.open("GET", key, true);

        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if (xhr.status != 200) {
                    AsynchronousSessionAuditor.errorCallback(key, xhr);
                }
                else {

                    var timeout = parseFloat(xhr.responseText)
                    if (isNaN(timeout) || (new Date(timeout) < new Date())) {
                        AsynchronousSessionAuditor.timeoutCallback(key, xhr);
                    }
                    else {
                        AsynchronousSessionAuditor.statusCallback(xhr.responseText);
                    }
                }

            }
        };
        xhr.send(null);
    },

    init: function(key, statusCallback) {
        // set the statusCallback member for reference.
        AsynchronousSessionAuditor.statusCallback = statusCallback;
        // check right now
        AsynchronousSessionAuditor.auditSession(key);
        // and recurring
        window.setInterval((function() { AsynchronousSessionAuditor.auditSession(key) }), AsynchronousSessionAuditor.pollingInterval);
    }
};



function callScriptMethod(url) {
    /// <summary>
    /// 
    /// Simply makes a bogus ScriptService call to a void PageMethod name DoSomething simulating
    /// an async (Ajax) call.
    /// This resets the session cookie in the same way a postback or refresh would.
    ///
    /// The same would apply to a ScriptService enabled XML Webservice call.
    /// </summary>

    var xhr = AsynchronousSessionAuditor.createXHR();
    xhr.open("POST", url, true);
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status != 200) {
                alert("script method call failed:" + xhr.statusText);
            }
        }
    };
    xhr.setRequestHeader("content-type", "application/json");
    var postData = null;
    xhr.send(postData);
}

<强>用法

<script src="AsynchronousSessionAuditor.js" type="text/javascript"></script>

<script type="text/javascript">

    function reportStatus(value) {
        /// <summary>
        /// In a typical session/ticket lifetime you might display a warning at T-5 minutes that the session 
        /// is expiring and require an action.
        /// </summary>

        document.getElementById("sessionTimeout").innerHTML = "Session expires  in " + parseInt((new Date(parseFloat(value)) - new Date()) / 1000) + " seconds.";
    }

    function init() {

        // default is 60 seconds. Our session is only 1 minute so lets get crazy and poll every second
        AsynchronousSessionAuditor.pollingInterval = 1000;
        AsynchronousSessionAuditor.init(AsynchronousSessionAuditor.aspSessionAuditKey, reportStatus);
    }

</script>

答案 1 :(得分:2)

通常,一个页面将使用JavaScript来保留计时器并在会话到期前一两分钟显示弹出或dhtml警告。我的银行这样做了。

这很烦人,因为如果用户有多个标签打开到同一个网站,一个标签会知道会话即将过期,但另一个标签不会,所以即使不是真的你也可以得到警告。如果您尝试使用AJAX调用向服务器询问会话到期之前的时间,您只需延长会话超时并有效地使会话永远不会过期,只要浏览器窗口打开(这可能不是很糟糕)取决于你的具体情况)。

答案 2 :(得分:1)

其他答案建议在页面上显示超时的通知..但我想您正在询问如何在用户重定向到的页面上显示消息。

一种可能性是仅在会话超时时将URL请求参数传递到起始页。然后,您的脚本或asp.net代码可以决定在参数出现时显示消息。

答案 3 :(得分:1)

在您的JavaScript重定向中,在您的重定向网址中添加一个查询字符串参数。

<script>
...
// If session is about to timeout...
location.href = '/home.aspx?action=expired';
...
</script>

然后在您的开始页面中,您可以使用代码来检查查询字符串并在这种情况下显示消息。

<%
'home.aspx
If Request.QueryString("action") = "expired" Then 
  Response.Write("<p>You have been logged out due to inactivity.</p>")
End If
%>