Предыдущая версия этого ответа содержит простой источник решения вопроса, которого больше не существует.
Сценарий состоял в том, что он хотел получить уведомление до того, как истекло время сеанса, чтобы можно было предпринять действие.
Неясно, был ли это сеанс 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>