Уведомить пользователя, когда время сеанса истекло в asp.net - PullRequest
4 голосов
/ 10 февраля 2010

Каков наилучший способ показать сообщение пользователю после истечения сеанса?К тому времени пользователь должен выйти из системы и перенаправиться на стартовую страницу.Я могу использовать JavaScript, чтобы перенаправить пользователя на стартовую страницу.Я просто хочу показать сообщение на стартовой странице.

Ответы [ 4 ]

15 голосов
/ 10 февраля 2010

Предыдущая версия этого ответа содержит простой источник решения вопроса, которого больше не существует.

Сценарий состоял в том, что он хотел получить уведомление до того, как истекло время сеанса, чтобы можно было предпринять действие.

Неясно, был ли это сеанс asp.net или билет проверки подлинности с помощью форм, но на самом деле это та же проблема.

По сути, это асинхронное управление состоянием сеанса без запуска обновлений сеанса / заявки. Распространенное желание для ajax-приложений.

Решение довольно простое, но не совсем прямое. Есть довольно много проблем, которые нужно решить, но я нашел решение, которое надежно работает как для простого состояния сеанса, так и для аутентификации форм.

Он состоит из очень легкого http-модуля и сопровождающей его библиотеки клиентских скриптов, не более 50 или 60 эффективных строк кода.

Стратегия заключается в предоставлении конечной точки http, которая может запрашивать состояние истечения сеанса или заявки, не обновляя их. Модуль просто находится в начале конвейера и обслуживает пару «виртуальных» конечных точек с датами javascript и очень простой js lib для выполнения вызовов.

Демо и источник здесь: AsynchronousSessionAuditor

Некоторые из вас задаются вопросом, почему я был бы обеспокоен этим, но, вероятно, многие из вас говорят, черт возьми, да, какая боль в патоо. Я знаю, что в прошлом у меня были хрупкие вонючие обходные пути, и я очень доволен этим.

Может использоваться в стандартном приложении с моделью обратной передачи или, скорее, в приложении ajax. Также... Вы можете узнать больше о жизненном цикле Http-запроса здесь и здесь . Если интересно, узнайте больше об использовании необработанного xmlhttp для конечных точек asp.net здесь

Вырезание предыдущего спайк-кода. проверьте историю изменений, если вы заинтересованы.

Вот соответствующие исходные файлы. Полная справочная реализация доступна по ссылке, указанной выше ....

HttpModule

// <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>
2 голосов
/ 10 февраля 2010

Обычно страница использует JavaScript, чтобы сохранить таймер и отображать всплывающее окно или dhtml предупреждение за минуту или две до истечения сеанса. Мой банк делает это.

Это очень раздражает, потому что, если у пользователя есть несколько вкладок, открытых на одном сайте, одна вкладка будет знать, что сеанс истекает, а другая - нет, поэтому вы можете получить предупреждение, даже если оно не соответствует действительности , Если вы пытаетесь использовать вызов AJAX, чтобы запросить у сервера время до истечения сеанса, вы просто продлили тайм-аут сеанса и фактически сделали так, что сеанс никогда не истекает, пока открыто окно браузера (что может быть не плохо вещь в зависимости от вашей конкретной ситуации).

1 голос
/ 10 февраля 2010

В своем перенаправлении JavaScript добавьте параметр строки запроса к URL-адресу перенаправления.

<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
%>
1 голос
/ 10 февраля 2010

В других ответах предлагается показывать уведомление на странице, для которого истекло время ожидания ... но я думаю, что вы спрашиваете, как показать сообщение на странице, на которую перенаправлен пользователь.

Одна из возможностей - просто передать параметр запроса URL на стартовую страницу только по истечении времени ожидания сеанса. Ваш скрипт или код asp.net может затем решить отображать сообщение, когда параметр присутствует.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...