Utrzymywanie otwartej / aktywnej sesji ASP.NET

116

Jaki jest najłatwiejszy i najbardziej dyskretny sposób na utrzymanie sesji ASP.NET, o ile użytkownik ma otwarte okno przeglądarki? Czy są to połączenia czasowe AJAX? Chcę zapobiec następującym sytuacjom: czasami użytkownicy otwierają okno przez długi czas, potem wprowadzają coś, a po przesłaniu nic już nie działa, ponieważ sesja po stronie serwera wygasła. Nie chcę zwiększać wartości limitu czasu na serwerze o więcej niż 10 minut, ponieważ chcę, aby sesje zamknięte (przez zamknięcie okna przeglądarki) szybko się kończyły.

Sugestie, próbki kodu?

Alex
źródło
Możesz sprawdzić ten link, aby również uzyskać odpowiedź dotnetcurry.com/ShowArticle.aspx?ID=453
Deweloper

Odpowiedzi:

171

Używam JQuery do wykonania prostego wywołania AJAX do fikcyjnego modułu obsługi HTTP, który nie robi nic poza utrzymaniem mojej sesji przy życiu:

function setHeartbeat() {
    setTimeout("heartbeat()", 5*60*1000); // every 5 min
}

function heartbeat() {
    $.get(
        "/SessionHeartbeat.ashx",
        null,
        function(data) {
            //$("#heartbeat").show().fadeOut(1000); // just a little "red flash" in the corner :)
            setHeartbeat();
        },
        "json"
    );
}

Procedura obsługi sesji może być tak prosta, jak:

public class SessionHeartbeatHttpHandler : IHttpHandler, IRequiresSessionState
{
    public bool IsReusable { get { return false; } }

    public void ProcessRequest(HttpContext context)
    {
        context.Session["Heartbeat"] = DateTime.Now;
    }
}

Kluczem jest dodanie IRequiresSessionState, w przeciwnym razie sesja nie będzie dostępna (= null). Program obsługi może oczywiście również zwrócić zserializowany obiekt JSON, jeśli niektóre dane powinny zostać zwrócone do wywołującego JavaScript.

Udostępnione przez web.config:

<httpHandlers>
    <add verb="GET,HEAD" path="SessionHeartbeat.ashx" validate="false" type="SessionHeartbeatHttpHandler"/>
</httpHandlers>

dodane z balexandre 14 sierpnia 2012 r

Ten przykład podobał mi się tak bardzo, że chcę ulepszyć HTML / CSS i część beatu

Zmień to

//$("#heartbeat").show().fadeOut(1000); // just a little "red flash" in the corner :)

w

beatHeart(2); // just a little "red flash" in the corner :)

i dodaj

// beat the heart 
// 'times' (int): nr of times to beat
function beatHeart(times) {
    var interval = setInterval(function () {
        $(".heartbeat").fadeIn(500, function () {
            $(".heartbeat").fadeOut(500);
        });
    }, 1000); // beat every second

    // after n times, let's clear the interval (adding 100ms of safe gap)
    setTimeout(function () { clearInterval(interval); }, (1000 * times) + 100);
}

HTML i CSS

<div class="heartbeat">&hearts;</div>

/* HEARBEAT */
.heartbeat {
    position: absolute;
    display: none;
    margin: 5px;
    color: red;
    right: 0;
    top: 0;
}

tutaj jest przykład na żywo tylko dla części bijącej : http://jsbin.com/ibagob/1/

veggerby
źródło
@veggerby "do fikcyjnego programu obsługi HTTP, który nic nie robi, ale utrzymuje moją sesję przy życiu". Czy możesz przesłać przykładowy kod programu obsługi HTTP, aby utrzymać sesję?
Gopinath
nie, a jedyną opcją (chyba) jest zwiększenie limitu czasu sesji, ale na dłuższą metę jest to prawdopodobnie zły pomysł
veggerby
Prowadziłem dochodzenie w sprawie powiązanego problemu i znalazłem to rozwiązanie. Dobry towar. Jednak jedno zapytanie, jeśli użytkownik pozostawi otwartą przeglądarkę i powie, że komputer nie zasypia przez 10 godzin, sesja będzie żyła przez tak długi czas, prawda? Czy to jest poprawne?
Julius A
2
Dobra robota, ale zawiodło mnie, dopóki nie dodałem serii pamięci podręcznej do połączenia. Bez tego parametru pamięci podręcznej kontroler jest wywoływany po raz pierwszy
jean
1
@stom ma być wartością sesji, a nie limitem czasu ani czymkolwiek. Powodem użycia DateTime.Nowbyło po prostu pokazanie, kiedy sesja została ostatnio zaktualizowana za pomocą pulsu.
veggerby
69

Jeśli używasz ASP.NET MVC - nie potrzebujesz dodatkowego modułu obsługi HTTP ani pewnych modyfikacji pliku web.config. Wszystko, czego potrzebujesz - wystarczy dodać prostą akcję w kontrolerze Home / Common:

[HttpPost]
public JsonResult KeepSessionAlive() {
    return new JsonResult {Data = "Success"};
}

, napisz kawałek kodu JavaScript, taki jak ten (umieściłem go w jednym z plików JavaScript witryny):

var keepSessionAlive = false;
var keepSessionAliveUrl = null;

function SetupSessionUpdater(actionUrl) {
    keepSessionAliveUrl = actionUrl;
    var container = $("#body");
    container.mousemove(function () { keepSessionAlive = true; });
    container.keydown(function () { keepSessionAlive = true; });
    CheckToKeepSessionAlive();
}

function CheckToKeepSessionAlive() {
    setTimeout("KeepSessionAlive()", 5*60*1000);
}

function KeepSessionAlive() {
    if (keepSessionAlive && keepSessionAliveUrl != null) {
        $.ajax({
            type: "POST",
            url: keepSessionAliveUrl,
            success: function () { keepSessionAlive = false; }
        });
    }
    CheckToKeepSessionAlive();
}

i zainicjuj tę funkcję, wywołując funkcję JavaScript:

SetupSessionUpdater('/Home/KeepSessionAlive');

Proszę zanotować! Zaimplementowałem tę funkcjonalność tylko dla autoryzowanych użytkowników (w większości przypadków nie ma powodu, aby utrzymywać stan sesji dla gości), a decyzja o utrzymaniu stanu sesji jest oparta nie tylko na - czy przeglądarka jest otwarta czy nie, ale autoryzowany użytkownik musi wykonać jakąś czynność na stronie (przesuń myszkę lub wpisz jakiś klawisz).

Maryan
źródło
3
Myślę, że dla MVC jest to lepsza odpowiedź. Nie musisz używać pliku .ashx, dlaczego miałbyś to zrobić?
arame3333
Dzięki obsłudze HTTP w asp mvc sprawdź to , nadzieja komuś pomoże.
shaijut
3
Maryan, możesz również chcieć użyć @Url.Action("KeepSessionAlive","Home")funkcji inicjalizatora, aby nie musieć zakodować adresu URL na stałe, a także wrzucić pierwszego bloku do IIFE i po prostu wyeksportować, SetupSessionUpdaterponieważ jest to jedyna rzecz, która musi być wywoływana zewnętrznie - coś w rodzaju to: SessionUpdater.js
KyleMit
Czy istnieje powód, dla którego setInterval nie jest używany? setInterval(KeepSessionAlive, 300000)
Matthieu Cormier
8

Za każdym razem, gdy wysyłasz żądanie do serwera, resetuje się limit czasu sesji. Możesz więc po prostu wykonać wywołanie ajax do pustego modułu obsługi HTTP na serwerze, ale upewnij się, że pamięć podręczna modułu obsługi jest wyłączona, w przeciwnym razie przeglądarka buforuje twój program obsługi i nie wykona nowego żądania.

KeepSessionAlive.ashx.cs

public class KeepSessionAlive : IHttpHandler, IRequiresSessionState
    {

        public void ProcessRequest(HttpContext context)
        {
            context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            context.Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(-1));
            context.Response.Cache.SetNoStore();
            context.Response.Cache.SetNoServerCaching();
        }
    }

.JS:

window.onload = function () {
        setInterval("KeepSessionAlive()", 60000)
}

 function KeepSessionAlive() {
 url = "/KeepSessionAlive.ashx?";
        var xmlHttp = new XMLHttpRequest();
        xmlHttp.open("GET", url, true);
        xmlHttp.send();
        }

@veggerby - Nie ma potrzeby narzutu przechowywania zmiennych w sesji. Wystarczy wykonać zapytanie do serwera.

BornToCode
źródło
2

Czy naprawdę musisz zachować sesję (czy masz w niej dane?), Czy wystarczy to sfałszować, przywracając sesję, gdy nadejdzie żądanie? Jeśli pierwszy, użyj powyższej metody. Jeśli drugi, spróbuj czegoś w rodzaju obsługi zdarzeń Session_End.

Jeśli masz uwierzytelnianie za pomocą formularzy, otrzymasz coś podobnego do Global.asax.cs

FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(formsCookie.Value);
if (ticket.Expired)
{
    Request.Cookies.Remove(FormsAuthentication.FormsCookieName);
    FormsAuthentication.SignOut();
    ...             
     }
else
{   ...
    // renew ticket if old
    ticket = FormsAuthentication.RenewTicketIfOld(ticket);
    ...
     }

Ustawiasz czas życia biletu na znacznie dłuższy niż czas trwania sesji. Jeśli nie uwierzytelniasz się lub używasz innej metody uwierzytelniania, istnieją podobne sztuczki. Wygląda na to, że interfejs sieciowy Microsoft TFS i SharePoint wydają się ich używać - okazuje się, że jeśli klikniesz łącze na nieaktualnej stronie, w wyskakującym okienku pojawi się monit o uwierzytelnienie, ale jeśli użyjesz tylko polecenia, działa.

Henry Troup
źródło
2

możesz po prostu napisać ten kod w swoim pliku skryptu java, to wszystko.

$(document).ready(function () {
        var delay = (20-1)*60*1000;
        window.setInterval(function () {
            var url = 'put the url of some Dummy page';
            $.get(url);                
        }, delay);
});

Jest (20-1)*60*1000to czas odświeżania, odświeży limit czasu sesji. Limit czasu odświeżania jest obliczany jako domyślny czas z iis = 20 minut, czyli 20 × 60000 = 1200000 milisekund - 60000 milisekund (jedna minuta przed wygaśnięciem sesji) to 1140000.

ANKIT KISHOR
źródło
0

Oto alternatywne rozwiązanie, które powinno przetrwać, jeśli komputer kliencki przejdzie w tryb uśpienia.

Jeśli masz ogromną liczbę zalogowanych użytkowników, używaj tego ostrożnie, ponieważ może to pochłonąć dużo pamięci serwera.

Po zalogowaniu (robię to w zdarzeniu LoggedIn kontrolki logowania)

Dim loggedOutAfterInactivity As Integer = 999 'Minutes

'Keep the session alive as long as the authentication cookie.
Session.Timeout = loggedOutAfterInactivity

'Get the authenticationTicket, decrypt and change timeout and create a new one.
Dim formsAuthenticationTicketCookie As HttpCookie = _
        Response.Cookies(FormsAuthentication.FormsCookieName)

Dim ticket As FormsAuthenticationTicket = _
        FormsAuthentication.Decrypt(formsAuthenticationTicketCookie.Value)
Dim newTicket As New FormsAuthenticationTicket(
        ticket.Version, ticket.Name, ticket.IssueDate, 
        ticket.IssueDate.AddMinutes(loggedOutAfterInactivity), 
        ticket.IsPersistent, ticket.UserData)
formsAuthenticationTicketCookie.Value = FormsAuthentication.Encrypt(newTicket)
Piotr
źródło
Dlaczego głosowano w dół? czy jest z tym jakiś problem, o którym nie wspomniałem? jeśli tak jest, podziel się nim, aby przyszli czytelnicy byli tego świadomi!
Peter
0

Spędziłem kilka dni, próbując wymyślić, jak przedłużyć sesję użytkownika w formularzach WebForm za pomocą wyskakującego okna dialogowego, które daje użytkownikowi opcję odnowienia sesji lub umożliwienia jej wygaśnięcia. Najważniejszą rzeczą, którą musisz wiedzieć, jest to, że nie potrzebujesz żadnego z tych fantazyjnych rzeczy „HttpContext”, które mają miejsce w niektórych innych odpowiedziach. Wszystko czego potrzebujesz to $ .post () jQuery; metoda. Na przykład podczas debugowania użyłem:

$.post("http://localhost:5562/Members/Location/Default.aspx");

a na swojej działającej witrynie użyłbyś czegoś takiego jak:

$.post("http://mysite/Members/Location/Default.aspx");

To takie proste. Ponadto, jeśli chcesz poprosić użytkownika o możliwość odnowienia sesji, wykonaj coś podobnego do następującego:

    <script type="text/javascript">
    $(function () { 
        var t = 9;
        var prolongBool = false;
        var originURL = document.location.origin;
        var expireTime = <%= FormsAuthentication.Timeout.TotalMinutes %>;

        // Dialog Counter
        var dialogCounter = function() {
            setTimeout( function() {
                $('#tickVar').text(t);
                    t--;
                    if(t <= 0 && prolongBool == false) {
                        var originURL = document.location.origin;
                        window.location.replace(originURL + "/timeout.aspx");
                        return;
                    }
                    else if(t <= 0) {
                        return;
                    }
                    dialogCounter();
            }, 1000);
        }

        var refreshDialogTimer = function() {
            setTimeout(function() { 
                $('#timeoutDialog').dialog('open');
            }, (expireTime * 1000 * 60 - (10 * 1000)) );
        };

        refreshDialogTimer();

        $('#timeoutDialog').dialog({
            title: "Session Expiring!",
            autoOpen: false,
            height: 170,
            width: 350,
            modal: true,
            buttons: {
                'Yes': function () {
                    prolongBool = true;
                    $.post("http://localhost:5562/Members/Location/Default.aspx"); 
                    refreshDialogTimer();
                    $(this).dialog("close");
                },
                Cancel: function () {
                    var originURL = document.location.origin;
                    window.location.replace(originURL + "/timeout.aspx");
                }
            },
            open: function() {
                prolongBool = false;
                $('#tickVar').text(10);
                t = 9;
                dialogCounter();
            }
        }); // end timeoutDialog
    }); //End page load
</script>

Nie zapomnij dodać okna dialogowego do swojego HTML:

        <div id="timeoutDialog" class='modal'>
            <form>
                <fieldset>
                    <label for="timeoutDialog">Your session will expire in</label>
                    <label for="timeoutDialog" id="tickVar">10</label>
                    <label for="timeoutDialog">seconds, would you like to renew your session?</label>
                </fieldset>
            </form>
        </div>
Colin
źródło
0

Jeśli chodzi o rozwiązanie veggerby, jeśli próbujesz zaimplementować je w aplikacji VB, uważaj, próbując uruchomić dostarczony kod przez tłumacza. Będzie działać:

Imports System.Web
Imports System.Web.Services
Imports System.Web.SessionState

Public Class SessionHeartbeatHttpHandler
    Implements IHttpHandler
    Implements IRequiresSessionState

    ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property

    Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
        context.Session("Heartbeat") = DateTime.Now
    End Sub
End Class

Ponadto zamiast wywoływać funkcję heartbeat (), taką jak:

 setTimeout("heartbeat()", 300000);

Zamiast tego nazwij to tak:

 setInterval(function () { heartbeat(); }, 300000);

Po pierwsze, setTimeout uruchamia się tylko raz, podczas gdy setInterval uruchamia się wielokrotnie. Po drugie, wywołanie heartbeat () jak napisu nie zadziałało dla mnie, podczas gdy wywołanie tego jak rzeczywistej funkcji tak.

I mogę absolutnie w 100% potwierdzić, że to rozwiązanie przezwycięży absurdalną decyzję GoDaddy o wymuszeniu 5-minutowej sesji Apppool w Plesku!

PokażJX1990
źródło
0

Tutaj wersja wtyczki JQuery rozwiązania Maryan z optymalizacją uchwytów. Tylko z JQuery 1.7+!

(function ($) {
    $.fn.heartbeat = function (options) {
        var settings = $.extend({
            // These are the defaults.
            events: 'mousemove keydown'
            , url: '/Home/KeepSessionAlive'
            , every: 5*60*1000
        }, options);

        var keepSessionAlive = false
         , $container = $(this)
         , handler = function () {
             keepSessionAlive = true;
             $container.off(settings.events, handler)
         }, reset = function () {
             keepSessionAlive = false;
             $container.on(settings.events, handler);
             setTimeout(sessionAlive, settings.every);
         }, sessionAlive = function () {
             keepSessionAlive && $.ajax({
                 type: "POST"
                 , url: settings.url
                 ,success: reset
                });
         };
        reset();

        return this;
    }
})(jQuery)

i jak to importuje w twoim * .cshtml

$('body').heartbeat(); // Simple
$('body').heartbeat({url:'@Url.Action("Home", "heartbeat")'}); // different url
$('body').heartbeat({every:6*60*1000}); // different timeout
AlexPalla
źródło
0

[Spóźniony na przyjęcie ...]

Innym sposobem, aby to zrobić bez narzutu wywołania Ajax lub modułu obsługi WebService, jest załadowanie specjalnej strony ASPX po określonym czasie (tj. Przed przekroczeniem limitu czasu stanu sesji, który zwykle wynosi 20 minut):

// Client-side JavaScript
function pingServer() {
    // Force the loading of a keep-alive ASPX page
    var img = new Image(1, 1);
    img.src = '/KeepAlive.aspx';
}

KeepAlive.aspxStrona jest po prostu pusta strona, która nie robi nic, ale dotyk / odświeżyć Sessionstan:

// KeepAlive.aspx.cs
public partial class KeepSessionAlive: System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // Refresh the current user session
        Session["refreshTime"] = DateTime.UtcNow;
    }
}

Działa to poprzez utworzenie elementu img(obrazu) i zmuszenie przeglądarki do załadowania jego zawartości ze KeepAlive.aspxstrony. Załadowanie tej strony powoduje, że serwer dotyka (aktualizuje) Sessionobiektu, wydłużając przedział czasu wygaśnięcia sesji (zwykle o kolejne 20 minut). Rzeczywista zawartość strony internetowej jest odrzucana przez przeglądarkę.

Alternatywnym i być może czystszym sposobem na to jest utworzenie nowego iframeelementu i załadowanie KeepAlive.aspxdo niego strony. iframeElement jest ukryte, takie jak wprowadzenie elementu dziecka ukrytego divelementu gdzieś na stronie.

Aktywność na samej stronie można wykryć, przechwytując działania myszy i klawiatury dla całej treści strony:

// Called when activity is detected
function activityDetected(evt) {
    ...
}

// Watch for mouse or keyboard activity
function watchForActivity() {
    var opts = { passive: true };
    document.body.addEventListener('mousemove', activityDetected, opts);
    document.body.addEventListener('keydown', activityDetected, opts);
}

Nie mogę przypisać sobie tego pomysłu; patrz: https://www.codeproject.com/Articles/227382/Alert-Session-Time-out-in-ASP-Net .

David R. Tribble
źródło