I've been working on this custom timer
I created for each user's login session
. Until now I was'nt able to make an individual timers
for each login session.
Scenario:
When User1
logs in, the timer will start counting
. When User2
logs in, the timer of Use1 will reset the value is same as the timer for User2.
It seems that they have one timer
(not individual).
Here is what I want to happen.
- when user1 logs in, his timer will start counting.
- if the timer reach 900 seconds(15 minutes) it will pop-up some modal box that tells that his session has time out.
- the modal will show a count down for at least 30 seconds
- after the count down, user will be logged out automatically
- Every user must have their own timers
I have done all of these except the last item Every user must have their own timers
Here is my code on creating a timer:
public class SessionTimer
{
private static Timer timer;
public static void StartTimer()
{
timer = new Timer();
timer.Interval = (double)Utility.ActivityTimerInterval();
timer.Elapsed += (s, e) => MonitorElapsedTime();
timer.Start();
}
public static void ResetTimer()
{
TimeCount = 0;
timer.Stop();
timer.Start();
}
public static int TimeCount { get; set; }
public static string ConnectionID { get; set; }
private static void MonitorElapsedTime()
{
if (TimeCount >= Utility.TimerValue())
{
timer.Stop();
Hubs.Notifier.SessionTimeOut(TimeCount);
}
else
{
Hubs.Notifier.SendElapsedTime(TimeCount);
}
TimeCount++;
}
}
After a successful login, I'm going to call the timer to start
[HttpPost]
public ActionResult SignIn(LoginCredentials info)
{
// Success full login
SessionTimer.StartTimer();
}
Here is the signalr code on server:
public class SessionTimerHub : Hub
{
public void SendTimeOutNotice(int time)
{
Clients.Client(Context.ConnectionId).alertClient(time);
}
public void CheckElapsedTime(int time)
{
Clients.Client(Context.ConnectionId).sendElapsedTime(time);
}
public void UpdateConnectionID(string id)
{
SessionTimer.ConnectionID = id;
}
}
public class Notifier
{
public static void SessionTimeOut(int time)
{
var context = GlobalHost.ConnectionManager.GetHubContext<SessionTimerHub>();
context.Clients.Client(SessionTimer.ConnectionID).alertClient(time);
}
public static void SendElapsedTime(int time)
{
var context = GlobalHost.ConnectionManager.GetHubContext<SessionTimerHub>();
context.Clients.Client(SessionTimer.ConnectionID).sendElapsedTime(time);
}
}
And the jquery code:
$(function () {
/////////////////////////////////////////////////// SESSION TIMER
var timer = $.connection.sessionTimerHub, $modaltimer = $('#session_timer_elapsed'), tt = null;
timer.client.alertClient = function (time) {
var $count = $modaltimer.find('.timer'), wait = 180;
$count.text(wait);
$modaltimer.modal('show');
tt = setInterval(function () {
$count.text(wait--);
if (wait < 0) {
$.post('@Url.Action("Logout", "Auth")', function () { window.location.reload(); });
window.clearInterval(tt);
}
}, 1000);
};
timer.client.sendElapsedTime = function (time) {
console.log(time);
};
$.connection.hub.start().done(function () {
timer.server.updateConnectionID($.connection.hub.id);
});
$modaltimer.on('click', '.still_here', function () {
$.post('@Url.Action("ResetTimer", "Auth")');
$modaltimer.modal('hide');
window.clearInterval(tt);
}).on('click', '.log_out', function () {
$.post('@Url.Action("Logout", "Auth")', function () { window.location.reload(); });
$modaltimer.modal('hide');
});
});
As you can see I'm doing this:
timer.server.updateConnectionID($.connection.hub.id);
to pass the connection id, because I can't get the id inside public class Notifier
My failed solutions
I tried putting the SessionTimer
in a session
using dynamic
and ExpandoObject
e.g:
public static dynamic Data
{
get
{
#region FAILSAFE
if (HttpContext.Current.Session[datakey] == null)
{
HttpContext.Current.Session[datakey] = new ExpandoObject();
}
#endregion
return (ExpandoObject)HttpContext.Current.Session[datakey];
}
}
And it successfully separated the timers. But when passing the connection id on my expandoobject variable
e.g:
public void UpdateConnectionID(string id)
{
MyExpandoObject.MySessionTimer.ConnectionID = id;
}
it throws null reference exception. It seems that my expandoObject is getting null when passing data from SignalR (Just my thinking), but I'm not sure for that.
Please help me with this individual timers and sending message to specific users when their timers has elapsed.
Please note
I want to create the timer on Server Side.
Resetting the timer from server
The timer must be able to reset on server. In this case I put custom attributes on every AcrionReseult
e.g.:
[HttpPost]
[BasecampAuthorize]
public ActionResult LoadEmailType()
{
return Json(Enum.GetNames(typeof(EmailType)).ToList());
}
When the user passes [BasecampAuthorize]
it means he made an activity.
Inside [BasecampAuthorize]
public class BasecampAuthorizeAttribute : AuthorizeAttribute
{
string url { get; set; }
public BasecampAuthorizeAttribute()
{
if (string.IsNullOrEmpty(url))
{
url = "~/SomeUrl";
}
}
public BasecampAuthorizeAttribute(string URL)
{
url = URL;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.HttpContext.Response.Redirect(url);
}
else
{
// MUST RESET SESSION TIMER HERE
}
base.OnAuthorization(filterContext);
}
}
@halter73 - How can I call the reset timer here?
In master page:
in cs page:
There already is a timer of the world: the clock of the user. Let the user's own time count down when he is logged in: save his personal log in time in a javascript cookie.
Let's say we're authenticating a user with an RSA key (
openssl genrsa 2048
).The convesation now goes:
The crux here is that you can't trust the client to provide a sane value of the count-down, so the above solution encrypts the time of login and uses that as the token.
This is from the client's perspective.
This is the challenge we get from the server (random garbage)
(Optional: make sure the client's signature works)
Now you can encode the client signature in a copy-able way:
Now let's sign an authentication token, so we're moving on to the server...
Here's what the server has run when set up:
Now that the server has a private and public key, let's create the token to be signed and used for authentication.
Because the text to encrypt is so tiny, we don't experience problems with key length on the server, and so we can use the key as-is rather than doing symmetric encryption.
This we give to the client. Every time we receive this token, we can validate that we signed it. If you have a server farm, make them sign with the same keys.
The server can validate the token again. Failure to decrypt the cipher means it has been modified and we fail the token. A production system should also verify a signature (in same envelope) to avoid corrupted data decrypting successfully.
Your problem is that you only have one static timer variable which is signle instance shared across an entire AppDomain. In ASP.NET, AppDomain's are per web application not per user. You can use a static variable, but that variable should be a collection holding a unique Timer for each connectionId. This will break down if you scale out behind a load balancer or IIS restarts the application which will obviously create a new AppDomain.
Since you store the connection id inside the
SessionTimer
class, you can simply pass it in as a parameter when calling methods in theNotifier
class.You don't need the
SendTimeOutNotice
orCheckElapsedTime
since you are calling the client methods in yourNotifier
class.UpdateConnectionID
can be replaced byOnConnected
.You should be creating your
SessionTimers
inside your hub so you can associate it with your connection id. You can do this inOnConnected
like above, but that means that you should only start your SignalR connection once you've logged in and you actually want to start yourSessionTimer
for that connection. It also helps to haveResetTimer
on the hub so you have the client's connection id. Alternatively, you could get the connection id on the client from$.connection.hub.id
and post it.EDIT:
If for some reason
SessionTimerHub.ResetTimer
is throwing aKeyNotFoundException
(which shouldn't happen if you are callingtimer.server.resetTimer
after$.connection.hub.start().done
fires), you could do the following:If for some reason IIS is restarting your application you may want to add this in
SessionTimerHub.OnReconnected
since clients will reconnect but your static SessionTimer.Timers will be reset and and all your SessionTimers will be gone.You aren't ever calling
SessionTimerHub.ResetTimer()
in C# right?