Is there a way regarding ASP.NET Webforms to set the time to live for a session variable to be 5 minutes?
For instance if I have the variable:
Session["Name"] = "Bob";
After 5 minutes I'd like it to be destroyed, or at least be able to nullify it.
Example:
Session["Name"] = null;
//But in the background and not code-controlled as it has to be live over multiple pages.
I know that these Session variables expire upon timeout which is controlled within IIS, but I'm trying to expire them before the timeout occurs at intervals of 5 minutes, is this possible?
Create two session values (or a single object which contains the two values, which I'd likely prefer but for simplicity let's just use two values to illustrate), one with the value and one with its expiry time.
Session["Name"] = "Bob";
Session["NameExpiry"] = DateTime.UtcNow.AddMinutes(5);
Then wherever you use that value, check its expiry:
if (Session["NameExpiry"] != null && DateTime.Parse(Session["NameExpiry"]) < DateTime.UtcNow)
{
Session["Name"] = null;
Session["NameExpiry"] = null;
}
You could throw in a variety of error checking, other logic on the values, etc. But the principal is the same. Essentially, don't think of it as trying to set some kind of background timer to actively expire the session data, think of storing the expiry time as part of the session data and silently expiring it the next time you use it after that time stamp.
Since sessions are stored on the server side. create an action that can be called after 5 minutes from the client side.
public class ControllerName
{
public ActionResult ClearSession()
{
Session = null;
}
}
Then on the client-side:
<script>
setTimeout(function(){ $.ajax('/ControllerName/ClearSession'); }, 300000);
</script>
After OP mentioned:
The issue I have with this is that it's client-side controlled. I need it to work in the case of users that are preventing JS from running on pages
Session["Name"] = "Bob";
//task that will run after the specified interval
Task.Delay(300000).ContinueWith((task) => {
Session.Remove(Name);
});
I have a client to OData system, and it (system) works in following way.
first I have to send a special request to retrieve a token.
then I make my requests, attaching token to each request.
at certain time request may fail, saying that token is outdated. then I should make another special request to get a new token
This is easy when I have only one thread. But I want to have multiple threads doing requests and all sharing the same token. Also if more than one concurrent requests fail with token being invalidated I want to send a special request exactly once and other clients to start using the updated token.
If it matters I am using C#.
Is there a common solution to synchronize such requests?
Without knowing much more about your implementation, one option could be MemoryCache.
Your threads could check the cache for a specific 'tokenkey' and get its value. You can set a expiration in your MemoryCache ahead of a known expiration if you wanted to prevent 401s or other unauthorized results.
Here's an example I use to get/set a new token required for auth header in web api calls:
private string GetNewToken()
{
lock (cacheLock)
{
// no token in cache so go get a new one
var newToken = TokenServiceAgent.GetJwt();
// number of minutes (offset) before JWT expires that will trigger update of cache
var cacheLifetime = 15
CacheItemPolicy cip = new CacheItemPolicy()
{
AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(cacheLifetime.Value))
};
MemoryCache.Default.Set("tokenkey", newToken, cip);
return newToken;
}
}
EDIT: can't get the code block to play nice in the SO editor
I am trying to keep track of connected users to my hub.
The way I tried to do this was by creating a custom Authorize attribute for my hub, and checking for the user that is trying to connect. If the user is already connected then the hub does not authorize the connection
public class SingleHubConnectionPerUserAttribute : Microsoft.AspNet.SignalR.AuthorizeAttribute
{
private static readonly HashSet<UserKey> connections = new HashSet<UserKey>();
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
Type hubType = hubDescriptor.HubType;
string userId = request.User.Identity.GetUserId();
UserKey userKey = new UserKey(hubType, userId);
if (connections.Contains(userKey) || !base.AuthorizeHubConnection(hubDescriptor, request))
{
return false;
}
connections.Add(userKey);
return true;
}
}
This would work fine if the method AuthorizeHubConnection was called only once per connection, but that is not what is happening.
When I load the page that tries to connect with the hub, AuthorizeHubConnection oddly runs multiple times, and the number of times it runs is not always the same, sometimes it's 5, some it's 3, I really have no clue of what could possibly be causing it.
Do you know what could cause AuthorizeHubConnection to get called more than once?
Authorization is invoked each time SignalR server receives an HTTP request before it does anything else (See: https://github.com/SignalR/SignalR/blob/dev/src/Microsoft.AspNet.SignalR.Core/PersistentConnection.cs#L161). While SignalR maintains a logically persistent connection it makes multiple HTTP Requests behind the scenes. When using Websockets transport you will typically see only 3 of these when starting the connection (for the negotiate, connect and start requests) and one for each reconnect. longPolling and serverSentEvents transport create an HTTP request each time to send data (send). In addition longPolling creates a polling HTTP request to receive data (poll). Each of these requests has to be authorized so this is the reason why you see multiple calls to the AuthorizeHubConnection method.
I have a distributed service that takes anywhere from 10 sec to 10 min to process a message. The service starts on a user's (== browser) request which is received through an API. Due to various limitations, the result of the service has to be returned outside of the initial request (timeouts, dropped client connections ...) Therefore I return a SessionId to the requesting user which can be used to retrieve the result until it expires.
Now it can happen that a user makes multiple consecutive requests for the result while the session is still locked. For example the following code gets hit with the same SessionId within 60 seconds:
var session = await responseQueue.AcceptMessageSessionAsync(sessionId);
var response = await session.ReceiveAsync(TimeSpan.FromSeconds(60));
if (response == null)
{
session.Abort();
return null;
}
await response.AbandonAsync();
What I need is a setup without locking and the ability to read a message multiple times until it expires plus the ability to wait for yet non-existent messages.
Which ServiceBus solution fits that bill?
UPDATE
Here's a dirty solution, still looking for a better way:
MessageSession session = null;
try
{
session = await responseQueue.AcceptMessageSessionAsync(sessionId);
}
catch
{
// ... and client has to try again until the 60 sec lock from other requests is released
}
var response = await session.ReceiveAsync(TimeSpan.FromSeconds(60));
// ...
Background:
Back at work, I'm working on a Web Forms App that uses default, sliding expiration for Session and FormsAuthentication time outs. My task is to track user inactivity and , if needed, display modal window with a warning about session expiration, timer and a button to refresh session time out (it would refresh forms authentication time out also). Button is wired to the event on the master page with an empty body, it does the job for now. (I'm aware of other solutions).
Partially working solution:
My approach was to use master page Page_PreRender method to get Session and FormsAuthentication time out values, compare them and pass smaller(or any) value to the client's browser (with other necessary data). In the browser, I would then display warning with the timer at some particular moment in modal window and it would all work as intended... that is...when the user is doing nothing.
Problem:
Our app has lots of pages with update panels. Those update panels are used to interact with and manipulate data stored in the Session. Whenever user does something inside one of those update panels, the session time out slides (restarts). I cannot track those things on the client (in the real world), I need a way to track Session expiration on the server.
Things I don't want to do
I dont' want go through every single page, every single gridview, every single imagebutton etc and to bind some client event to track user actions. I want more... generic and maintainable solution if possible.
My idea:
I would like to be able to track/check Session or FormsAuthentication remaining time before expiration occurs OR to be notified if those values have been reseted. That way I could have initial data on the client about the moment in time when I should display the modal window with warning, and at that time, I would like to be able to check with the server first IF that time is right (approximately) or if I should update my javascript data on the client and prolong the time before displaying warning window since the session expiration has been updated...
And what about intercepting and filtering user requests ? Would that help ?
Have you considered Session State via SQL Server? It allows you to manage sessions via the database, opening up the option to query the expiration information you desire. Check out this link. Or this link.
Implement IHttpModule, attach to appropriate HttpApplication events and in its do what you want.
Although it doesn't do exactly what you're asking for, perhaps this Timeout control might at least provide some inspiration?
*Update: There's some comments on the blog from a guy implementing the control with sliding expirations and how he made it work.
First of all, thank you all for your suggestions. I've managed to solve this problem last week, just didn't had time to post earlier... and frankly, I was interested in other solutions. I'll make sure to read all the material you posted and suggested. Thank you for that.
Here's what I did:
On the server side:
I used cookies to track session expiration time. One thing that slipped my mind was that Page goes through its whole life cycle even on asynchronous requests. The whole idea was to use Master Page's Pre_Render event to set the appropriate cookies on EVERY request.
I was using UTC time format and was calculating milliseconds that are translatable to javascript time format :
var tmpDate = new DateTime(1970, 1, 1);
var nowUtc = DateTime.UtcNow;
var tmpSpan = new TimeSpan(nowUtc.Ticks - tmpDate.Ticks);
return tmpSpan.TotalMilliseconds;
With this, I had everything I needed for my calculations, current time, session expiration (Session.Timeout) time, and time for warning... basic math from there.
On the client side:
First, I red all three cookies to start tracking up and initialized two separate variables for tracking current server time. They were both initialized to the same value of the first cookie read of the current server time.
Then, I've used setTimeout function to read cookie (current server time) every second (jQuery plug-in). I was checking if current server time from former cookie read and current server time from latest cookie read were equal(separate variables) - that way I knew if there were any requests made and if there were, I would read 2 other cookies for warning time and time-out time to update things up and close warning dialog if it was opened. After that I would make current server time variables equal and continue comparing them after every cookie read and updating only one of them.
Also, on every setTimeout call I'm getting current time with
new Date().getTime();
which returns current UTC time measured on the client machine. In any case we rely on the presumption that client has valid clock set-up on his/her machine. After that I'm comparing current time with warning time and if current time is "bigger" that the warning time and "smaller" than the time-out time, then we have to display popup.
If the current time is "bigger" than the time-out time, click invisible button that calls method in the masters page code behind that logs out user. This works great even when user has multiple tabs open in browser. Once the user is logged out in one tab, every other page(tab) that tries to log him out also, gets redirected to login page.
If the user click OK button to confirm his/her presence, an empty event in masters page code behind is triggered (click hidden asp button). This is enough to refresh the session. This will automatically resend cookies, and everything goes as intended. (given that you have sliding session).
It was easy to implement timer in the warning window. I've dynamically created div for modal window with message and 3 spans. On every setTimeout iteration I was setting timer like this:
Calculate Hours, Minutes, Seconds from current javascript time
Place result in variable
If the result is smaller than 10 then prepend 0 (zero)
Target appropriate span by class(had problems with ID in IE 8, used unique classes) and set its text property to the calculated value $(obj).text(calculatedValue)
For The End
Make sure that, when you are reading cookies to parse them as floats, just to be sure.
I'm interested to see the application of server push technology and usage of web sockets in this particular situation but I think it would be an overkill if you would track and calculate session expiration on the server for every client.
I know that I've asked for session expiration tracking on the server and that this looks like tracking on the client, but it's not... those cookies are all we need for tracking and they come from server so... that's about it.
Here is my solution.
Ensure that FormsAuthentication.CookieMode is set to use cookies (it is by default).
Add new key to Session object and populate it in each page request (master-page's events like Page_Init, Page_Load etc.), include postbacks and callbacks.
Session["SessionUserLastPageRequestTime"] = MyApp.CommonUtils.GetServerDateTime();
Create a HttpHandler, which implements IReadOnlySessionState interface.
public class SessionStateHandler : IHttpHandler, IReadOnlySessionState
Place the SessionStateHandler haldler into folder with allowed anonymous access (or just put it to root folder).
public void ProcessRequest(HttpContext context)
{
string requestData, alertMessage;
using (var reader = new StreamReader(context.Request.InputStream))
{
requestData = reader.ReadToEnd();
}
/* -1: unauthorized;
* -2: authorized, but no information about last request time;
* -3: more than half session timeout, or not needed to inform user about session expiration;
* >=0: lesser than half session timeout or if needed to inform user. The number reflects the value of total amount of minutes to session expiration.
*/
int status = -1, idleTimeInMinutes = 0;
if (context.User?.Identity == null
|| !context.User.Identity.IsAuthenticated
|| context.Session == null
|| context.Session["SessionUserLastPageRequestTime"] == null)
{
status = -1;
alertMessage = "Session expired!";
}
else
{
DateTime now = MyApp.CommonUtils.GetServerDateTime();
var userLastPageRequest = (DateTime?)context.Session["SessionUserLastPageRequestTime"];
if (userLastPageRequest == null)
{
status = -2;
alertMessage = null;
}
else
{
var halfTimeout = TimeSpan.FromMinutes(context.Session.Timeout / 2);
TimeSpan idleTime = now.Subtract(userLastPageRequest.Value);
idleTimeInMinutes = (int)idleTime.TotalMinutes;
if (idleTime <= halfTimeout)
{
status = -3;
alertMessage = null;
}
else
{
status = context.Session.Timeout - idleTimeInMinutes;
/* Send time to session expiration only if 3 or lesser minutes remain */
if (status > 3)
status = -3;
/* If session expiration time's expanding (sliding expiration) has been done in some way */
else if (status < 0)
status = 0;
alertMessage = string.Format("Session Will Expire In {0} Minutes", status);
}
}
}
context.Response.Write(JsonConvert.SerializeObject(new { Status = status, Message = alertMessage }));
/* Remove new cookie to prevent sliding of session expiration time */
if (requestData == "TIMEOUT_REQUEST")
context.Response.Cookies.Remove(FormsAuthentication.FormsCookieName); /* by fact FormsAuthentication.FormsCookieName equals to ".ASPXAUTH" */
}
Note that server will slide session expiration only if request is detected after at least half of session timeout time is gone since session start, e.g. if Session.Timeout is set to 30min, new cookie with ticket will be send to client in response on first request after 15min since session start.
So as far as you want to KNOW the session expiration time, and not to EXTEND it instantly, then you need to remove new ASP.NET auth. cookie from response (look at line context.Response.Cookies.Remove(FormsAuthentication.FormsCookieName);).
Create continious request from client side by using setInterval or setTimer
var stc_ContiniousRequestOfSessionState_IntervalId;
function stc_ContiniousRequestOfSessionState() {
stc_ContiniousRequestOfSessionState_IntervalId = setInterval(stc_ContiniousRequestOfSessionState_OnInterval, 20000);
}
function stc_ContiniousRequestOfSessionState_OnInterval() {
stc_RequestSessionStateFromHttpHandler("TIMEOUT_REQUEST");
}
function stc_RequestSessionStateFromHttpHandler(dataToSend) {
var url = '<%: Page.ResolveClientUrl("~/handlers/SessionStateHandler.ashx") %>';
$.ajax({
type: "POST",
url: url,
contentType: "application/json",
dataType: "json",
data: dataToSend,
success: function (result) {
stc_sessionState = result.Status;
if (stc_sessionState == -2 || stc_sessionState == -3) {
hideSessionEndWarningMessage();
}
else if (stc_sessionState == -1) {
sessionExpired = true;
clearInterval(stc_ContiniousRequestOfSessionState_IntervalId);
showSessionTimeoutWarningMessage(result.Message);
}
else if (stc_sessionState >= 0) {
showSessionTimeoutWarningMessage(result.Message);
}
},
error: function (result) {
/* we can increase request frequency, re-send session request instantly or do something else at this place */
}
});
}
run JS function on page load by adding it to startup scripts on master-page.
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if (Page.User.Identity.IsAuthenticated)
{
ScriptManager.RegisterStartupScript(this, this.GetType(), "test1", "continiousRequestOfSessionState();", true);
}
}
}