How to create Login Session Timer for individual u

2019-07-25 01:29发布

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?

3条回答
迷人小祖宗
2楼-- · 2019-07-25 01:58

In master page:

public partial class MasterPage : System.Web.UI.MasterPage
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Session["LoginID"] != null)
            Label1.Text="Welcome :: " + Session["LoginID"].ToString();
        else
            Response.Redirect("Login.aspx");

    }
    protected void lnkLogout_Click(object sender, EventArgs e)
    {
        Session["LoginID"] = null;
        Response.Redirect("Login.aspx");

    }
}

in cs page:

protected void Login_Click(object sender, EventArgs e)
    {
        string conString = "Provider=Microsoft.JET.OLEDB.4.0; data source=" + Server.MapPath (string.Empty)  + @"\Database\Northwind.mdb";
        string sqlString = "SELECT * FROM CUSTOMERS where CustomerID='" + TextBox1.Text + "' and City='" + TextBox2.Text + "'";
        OleDbConnection conn = new OleDbConnection(conString);
        DataSet ds = new DataSet();
        OleDbDataAdapter adapter = new OleDbDataAdapter(sqlString, conn);
        adapter.Fill(ds);
        if (ds != null)
        {
            if (ds.Tables[0].Rows.Count > 0)
            {
                Session["LoginID"] = ds.Tables[0].Rows[0]["CustomerID"].ToString();
                Response.Redirect("Welcome.aspx");

            }
            else
                Label1.Text = "Enter correct id/city";
        }


    }
}
查看更多
3楼-- · 2019-07-25 02:10

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:

> GET /login

< 200 OK
challenge: mie4NaNgfcqwkhYtNiy4oF

<form action='/login'>
  <input type='text' name='user-public-key' />
  <input type='text' name='rsa-signed-challenge' />
</form>

> POST /login
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA24PnkIZYg/k6lLbNZl5X
IxNl8KEsJTUX7h9B2P+o5LjB6e1OozVwsPFwXjCJP3WN8KwBQhvpxH4ZCK9H0LyT
+NXBPCsydBgY2VfqKK3wQONRKUaEQkSlV/gA0ciWa/jXD9FDbc3onrlqNTmtjGiM
OkXzSJDPz67tEDx+sZQ/+zcEO9tVPSioq6tZwMHx3EfBratA9W148OZRLOS1AFmc
RSSJzsgj/MmOTxrGjfNE2dKMvul4usjWf8Of+GEpEqW6+SrKZ3ivhP/jx+q1v8Bp
pbhDYag6I+YTwEu4feXJEgM4z83e05DJvh3hUDgzv7KDMqCp/h3m8f3TolcgvNox
fQIDAQAB
-----END PUBLIC KEY-----

rsa-signed-challenge:
IyGoX2aqQOH/tBRxxxGPIWGmbo2r6KfEmcDUdDvt7nNaN0GwqXm6MZaJ6eQvjkCR
AVnVRpJNGQDWCaLqzyBLVrysTlnC0dHkJD/vJg3jv74UqZGOqeKSW06HIhzz79UY
RghkdjadDpA8Jzs8NSYBzFopExfzSz+K1sOOwVXWa9nywhGqEj7XXoJO1I0j+o63
Wt94xEa30gmW1oVWIvjWLnBewH4H9ZzXv8PeGTdLdp2v9c9a3nsd7PsYi2yHul+S
CfAlFo/hITfEqucUX5zgyJyU0+SAVRod+vRlSaimMW2CQq8K8kQSbADaQpET4pa1
9eVnG99rz2UHw9b6UG0nBQ==

< 301 Redirect /dashboard
< Set-Cookie: Token=
iPtyfAxAcenFog1kn/h5VrdkBj0wxjeZBnB4JfXLcOWlh+G6/GdndgT7VTg3rSyq
uNFfbgnKNQtiGBjJ45cUHDai44ILK0g6DXPcicusEhs30xJhh8CT4P2FfK/juTtz
d0nj9ypncFOsUPAzJdirgdxO9oMquw0DW2b/iG1O/Dn2fU1/lDrmFpKKIMnPZ10g
cD6gY7Yhqs+2OcyfuiuIBYf2tAq8LYtKSm69j0AlgEiFNNmPl12Wr1R7YhxJZ1hi
smDjNTom5tDfxi3LDfwFtsHKn61OCbfuI3PhlPPqYTUd6omL1Efbr29l4jj+9cgF
0NnaX1L6LUUd6RaVmCw30w==


> GET /dashboard
> Cookie: Token=
iPtyfAxAcenFog1kn/h5VrdkBj0wxjeZBnB4JfXLcOWlh+G6/GdndgT7VTg3rSyq
uNFfbgnKNQtiGBjJ45cUHDai44ILK0g6DXPcicusEhs30xJhh8CT4P2FfK/juTtz
d0nj9ypncFOsUPAzJdirgdxO9oMquw0DW2b/iG1O/Dn2fU1/lDrmFpKKIMnPZ10g
cD6gY7Yhqs+2OcyfuiuIBYf2tAq8LYtKSm69j0AlgEiFNNmPl12Wr1R7YhxJZ1hi
smDjNTom5tDfxi3LDfwFtsHKn61OCbfuI3PhlPPqYTUd6omL1Efbr29l4jj+9cgF
0NnaX1L6LUUd6RaVmCw30w==

< 200 OK
<html>
<head>
  <script>
    jQuery.fn.startCountDown = function(el, opts) {
      opts = $.extend({ minutes : 20 }, opts);
      var start = new Time();
      var updater = function() { el.text("Seconds left: " + (opts.minutes * 60 - ((new Time()) - start))); }
      setTimeout(1000, updater);
    }
    $(function() {
      $("#timer").startCountDown({ minutes: 30 });
    })
  </script>
</head>
<body><p id="timer"></p></body>
</html>

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.

$ openssl genrsa 2048 >client.key
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA24PnkIZYg/k6lLbNZl5XIxNl8KEsJTUX7h9B2P+o5LjB6e1O
ozVwsPFwXjCJP3WN8KwBQhvpxH4ZCK9H0LyT+NXBPCsydBgY2VfqKK3wQONRKUaE
QkSlV/gA0ciWa/jXD9FDbc3onrlqNTmtjGiMOkXzSJDPz67tEDx+sZQ/+zcEO9tV
PSioq6tZwMHx3EfBratA9W148OZRLOS1AFmcRSSJzsgj/MmOTxrGjfNE2dKMvul4
usjWf8Of+GEpEqW6+SrKZ3ivhP/jx+q1v8BppbhDYag6I+YTwEu4feXJEgM4z83e
05DJvh3hUDgzv7KDMqCp/h3m8f3TolcgvNoxfQIDAQABAoIBAQCg8VrsObPYPvjW
ZBjAf1a/3s8U1/Z36S98ZOpwYTHBUDzMeDL5sorHEJ3kUQ2vu06wMExT3gdNC27r
USgEQN70yDP/G2TIfYpqf+ysmqrVyFSPQKZjt9TKZIilRr4St8VmUXVwolF1Xlgi
YgF+OoDlkLfIcnQKvyQMjW4OYLVwRw+bCKAq/T45kki+X41VK4Ubsnjddy+yT++3
GKMPqezVmZHGuhyVtR+dB9vQB3zWZocQRrqDDoQviDB7+scQD2XeWz53SUScBdwm
TzW5YaYclbttVWib0okCSxnhF3yah8cqvQHgqalrACnhaQx9oLcqyrg6KLCI/Lnn
yOZLvKZ5AoGBAP46Y8waGAYgPtgI/7Rni4NslnANGh6psWKaELftPLThxiuCoHad
rMpSojqP8FbKgqILDUOwczPIy/+jWK6J8EJPv4za2dlOcGnnfzD3Ko6LN2Jq3reC
N0Ywi3esTaesHp/SDOB39xW3XAAkdsejffy+LHsnrpVKYKlVm43Jhy6nAoGBAN0L
k+0m9RgwOxWiiAJK3XSBgdHK0e9BnVMKFVPiS2Q1wrnOUDb1CansAAyYSVZYsJZD
eh5VocYPGPTydl/muywlPceGgW2O1CO/LZPj1cKQANHx0+6F9itmk8leaTR4rTuf
2G67sdvlYCyKPoIvVg3WJQnj3dsqo4Ldt/5KkSc7AoGBAMDbIqHOmbLr+0B/cxs0
AY3tbiIKjmn8aOhX357nhUnijCatrXTOICpLjW3Hi5cLgRXUNHfI/1ulU7vV+oxN
b8meHb2IuAI1kumEB+TpW4tO6PDsCZBEZBIG+YYLW816sLCk88fEudfrhQtGniTM
TeLRkYTLkZEHH1TV8G8bFkW5AoGAXf9XZ2jCnwebiIa2KatmYu3To8AI6CJR4YcP
LL21a6bE6LiIOeaXtm+KUdDMlvBeH3gQTSgDBDNVXIxitENs4sfvbpKPJWSwZ4cb
vaEMPJF6F80rX2oOFcSoIeCJAmwy1oERy3z7lFQFQsuC619vy7B9zafdpx6Jq9PX
M0bIVRMCgYEAy9LvPbyXMWYmOy9svYRy4iAL9sCRNmzZgvC5B5b3lN55EWoE8ipw
g4qNBv3NaCJ/lTFezRQZVRfFfQOpNGDvJBLmTEaSR3OuV8hRw7zttC3/MazDqwTy
qZeg526uklXN7IvkFfiHlYZeed1u4wc7SXSi76RIE0w5lDBIb+CPPM4=
-----END RSA PRIVATE KEY-----

$ openssl rsa -in client.key -pubout >client.key.pub
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA24PnkIZYg/k6lLbNZl5X
IxNl8KEsJTUX7h9B2P+o5LjB6e1OozVwsPFwXjCJP3WN8KwBQhvpxH4ZCK9H0LyT
+NXBPCsydBgY2VfqKK3wQONRKUaEQkSlV/gA0ciWa/jXD9FDbc3onrlqNTmtjGiM
OkXzSJDPz67tEDx+sZQ/+zcEO9tVPSioq6tZwMHx3EfBratA9W148OZRLOS1AFmc
RSSJzsgj/MmOTxrGjfNE2dKMvul4usjWf8Of+GEpEqW6+SrKZ3ivhP/jx+q1v8Bp
pbhDYag6I+YTwEu4feXJEgM4z83e05DJvh3hUDgzv7KDMqCp/h3m8f3TolcgvNox
fQIDAQAB
-----END PUBLIC KEY-----

This is the challenge we get from the server (random garbage)

$ echo "mie4NaNgfcqwkhYtNiy4oF" >to-sign
$ openssl dgst -sha1 -sign client.key -out to-sign.sha1 to-sign

(Optional: make sure the client's signature works)

$ openssl dgst -sha1 -verify client.key.pub -signature to-sign.sha1 to-sign

Now you can encode the client signature in a copy-able way:

$ cat to-sign.sha1 | openssl base64 -e
g8nbi3mrcZ8afEpf4iRG6TQFDJtxw48AJ0QTKOdTKh2khxbQdPQIxAFWU++xsJRd
m0wnKRc1SpEYxhLeMKYjyhMTDce/KUBM/5i1Hoh9RPi9+G/cxNoXJiVKaxIF+rDf
NYk7mQD9ofdYgBCAbu2hi6jR5t2BY6emd7z/F53E8edVFtzqlHYjVLHNNYiNlXqD
WHl9OLV2b+yHY/mAkgUBYoVjyBvFnHFcRxTrZMC9A8K3jfU/bEOLgxEmq3UgmvWC
2M7fwpOBAUN7q9yoIR/kOGNPqePghhHTuyeFtbC33PmD8qgbgVUQby8jpVQ7T+5Q
kjEP2sbnETHlkjS6TXSKHg==

Now let's sign an authentication token, so we're moving on to the server...

$ openssl genrsa 2048 >server.key
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAoD80ZhPHm1x62wAESbEdHmzCdYCwC3VwkOwDMr4fD9Qqmw6D
Zu2oMSAJ8Kw1Q4oRJd3sZkHu7ZnCwHTe3P83uBKrCfDDFiu/aB1mJQJDAZzPwh5r
W8/2FAHmq/iw/f3U3IOaPuE1w7eoEaqFuNnmfb0EEBhsi7zhbjKpKowUXObPz1W5
mIJvpWqlyygHG6+JJVMs1jTdwzzQzcyHZDgEu/fODHgW5ErBQS7fKOOvZZIxEuLg
i7x5xB/mMYpWdIO0BI/xcu3SdmmNE/Ix8MoAZXXTK9u8Uu+2jnO69pFWtBGPIa2l
AGKNQ1jlUgvECglo356OOpKx9rb9Fd9WY4SIxwIDAQABAoIBABIiFNPYOSYjeON/
RPzxxdHDjN2vCjzBtVMw4cvEJ8+quoeBRO1Ix1eHwJgzZHOYFAis7CtGGrtYQul0
UCPB3ZQ+yIv/apP/r1EgwoY9k0eDbx8QQiXJipcJAAlFwwF6z7OEUNf8tBDJn4Mg
QLGCNsrTsLoBiYbmgLvvj6T45PT+G3ztaETv7iiiUIMX5R/7thK/+odiUeqyIbHH
m0673m0nYdZvGe7ujZarbh1h2x4Srs+OiMaPXH1ehw/nTvYAoEgGvz8eGVFCUrfa
87rizYMLYk8zs/TAz4CDtAk9PtLVMS6NZdKCYe4zvLBdjGjwx7gQJFpjXJCRQwA2
JGE8U8ECgYEAzn4I5yHc+ApzJDJyRmeR70MMWk0KpzIi+8PhwKUNpdP3DyKGOTAs
Z3g3su1/C5tcq2GIK/hprvDxdtg7wu7RSZdhz1W3Ee584JTqzqAG464bmCqd1aPi
sp7DVOeSrb5EZDtrZ9UpW79R2skRhU3qLAay0bl/cvKauBukCr0gUUsCgYEAxqq+
slkXI1tvCzaoxAWNyf0hooGKjEeAsCyrFYJwVk9cveJ0wMZUC7pLPH9UVEIjZzzo
Fm+xr5kVbXhLBn9cCXjP/OgBtuM1KqA/mcsmysVn2okuU8BIy43RYXgq1+eUI92/
MDFKtuFzMikSqI5gY3sIB0GRfL19FAvdy4iKtPUCgYEApUiJA8k9QGXM6Epg4i4A
yA1ZE+bbAh3FltSiHTuAgx35genWmmwO/vthSh2ENdwz/xJglyGOJnPCM6i9nTjf
2RINPpKTqQzGdFV+5cl9+jzg5ZonIFzAFs2x+IIsDFpiEADn5gLfygqIEKIlHhjR
uk/aTrk2ZOIAKiIl2lqsRaUCgYA3K8O5k7QxRXsZChzkEwbFSV7F2mO3gUPjqQP5
/TdlQLToprL1th4xA5NRQasRmyxpxyhM0sftk/23YOi07TmKB9r6yRNwzrg9FjOT
ai9jsF6e+em7qHKO1NuIze5X9x/UtggaQhYVo5ZyH6Xm2WM7PTeFjFfy5EyP/Juj
ok+i4QKBgQCf6mndYOkBCmzyMv2gASr8nj7Fh0oTxfaFRrs9k/DnUTOSUgrHrmO4
eCAk/D5FdPjcmF1np8wasVt38sw5nxUmbYonoV/2H+xmvKrRqtuflGRQx98P/+Qd
6vIF/n3NM66oZG9zgeYdEzxAbLXptCO60arJ4Ekrod/J+EpGSQb+bg==

Here's what the server has run when set up:

$ openssl rsa -in server.key -pubout >server.key.pub
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoD80ZhPHm1x62wAESbEd
HmzCdYCwC3VwkOwDMr4fD9Qqmw6DZu2oMSAJ8Kw1Q4oRJd3sZkHu7ZnCwHTe3P83
uBKrCfDDFiu/aB1mJQJDAZzPwh5rW8/2FAHmq/iw/f3U3IOaPuE1w7eoEaqFuNnm
fb0EEBhsi7zhbjKpKowUXObPz1W5mIJvpWqlyygHG6+JJVMs1jTdwzzQzcyHZDgE
u/fODHgW5ErBQS7fKOOvZZIxEuLgi7x5xB/mMYpWdIO0BI/xcu3SdmmNE/Ix8MoA
ZXXTK9u8Uu+2jnO69pFWtBGPIa2lAGKNQ1jlUgvECglo356OOpKx9rb9Fd9WY4SI
xwIDAQAB
-----END PUBLIC KEY-----

Now that the server has a private and public key, let's create the token to be signed and used for authentication.

$ echo "2013-02-09T16:33:44+0000" >auth.token.plain

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.

$ openssl rsautl -encrypt -inkey server.key.pub -pubin -in auth.token.plain -out auth.token.cipher

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.

$ cat auth.token.cipher | openssl base64 -e
iPtyfAxAcenFog1kn/h5VrdkBj0wxjeZBnB4JfXLcOWlh+G6/GdndgT7VTg3rSyq
uNFfbgnKNQtiGBjJ45cUHDai44ILK0g6DXPcicusEhs30xJhh8CT4P2FfK/juTtz
d0nj9ypncFOsUPAzJdirgdxO9oMquw0DW2b/iG1O/Dn2fU1/lDrmFpKKIMnPZ10g
cD6gY7Yhqs+2OcyfuiuIBYf2tAq8LYtKSm69j0AlgEiFNNmPl12Wr1R7YhxJZ1hi
smDjNTom5tDfxi3LDfwFtsHKn61OCbfuI3PhlPPqYTUd6omL1Efbr29l4jj+9cgF
0NnaX1L6LUUd6RaVmCw30w==

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.

$ openssl rsautl -decrypt -inkey server.key -in auth.token.cipher
2013-02-09T16:33:44+0000
查看更多
forever°为你锁心
4楼-- · 2019-07-25 02:19

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.

public class SessionTimer : IDisposable
{
    public static readonly ConcurrentDictionary<string, SessionTimer> Timers;

    private readonly Timer timer;

    static SessionTimer()
    {
        Timers = new ConcurrentDictionary<string, SessionTimer>();
    }

    private SessionTimer(string connectionID)
    {
        ConnectionID = connectionID;
        timer = new Timer();
        timer.Interval = (double)Utility.ActivityTimerInterval();
        timer.Elapsed += (s, e) => MonitorElapsedTime();

        timer.Start();
    }

    private int TimeCount { get; set; }

    private string ConnectionID { get; set; }

    public static void StartTimer(string connectionID)
    {
        var newTimer = new SessionTimer(connectionID);
        if (!Timers.TryAdd(connectionID, newTimer))
        {
            newTimer.Dispose();
        }
    }

    public static void StopTimer(string connectionID)
    {
        SessionTimer oldTimer;
        if (Timers.TryRemove(connectionID, out oldTimer))
        {
            oldTimer.Dispose();
        }
    }

    public void ResetTimer()
    {
        TimeCount = 0;
        timer.Stop();
        timer.Start();
    }

    public override Dispose()
    {
        // Stop might not be necessary since we call Dispose
        timer.Stop();
        timer.Dispose();
    }

    private void MonitorElapsedTime()
    {
        if (TimeCount >= Utility.TimerValue())
        {
            StopTimer(ConnectionID);
            Hubs.Notifier.SessionTimeOut(ConnectionID, TimeCount);
        }
        else
        {
            Hubs.Notifier.SendElapsedTime(ConnectionID, TimeCount);
        }

        TimeCount++;
    }
}

Since you store the connection id inside the SessionTimer class, you can simply pass it in as a parameter when calling methods in the Notifier class.

public static class Notifier
{
    private static context = GlobalHost.ConnectionManager.GetHubContext<SessionTimerHub>(); 

    public static void SessionTimeOut(string connectionID, int time)
    {
        context.Clients.Client(connectionID).alertClient(time);
    }

    public static void SendElapsedTime(string connectionID, int time)
    {
        context.Clients.Client(connectionID).sendElapsedTime(time);
    }
}

You don't need the SendTimeOutNotice or CheckElapsedTime since you are calling the client methods in your Notifier class. UpdateConnectionID can be replaced by OnConnected.

public class SessionTimerHub : Hub
{
    public override Task OnConnected()
    {
        SessionTimer.StartTimer(Context.ConnectionId);
        return base.OnConnected();
    }

    public override Task OnDisconnected()
    {
        SessionTimer.StopTimer(Context.ConnectionId);
        return base.OnDisconnected();
    }

    public void ResetTimer()
    {
        SessionTimer.Timers[Context.ConnectionId].ResetTimer();
    }
}

You should be creating your SessionTimers inside your hub so you can associate it with your connection id. You can do this in OnConnected like above, but that means that you should only start your SignalR connection once you've logged in and you actually want to start your SessionTimer for that connection. It also helps to have ResetTimer 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.

$.connection.hub.start().done(function () {
    $modaltimer.on('click', '.still_here', function () {
        timer.server.resetTimer();
        $.post('@Url.Action("ResetTimer", "Auth")');
        $modaltimer.modal('hide');
        window.clearInterval(tt);
    });
});

EDIT:

If for some reason SessionTimerHub.ResetTimer is throwing a KeyNotFoundException (which shouldn't happen if you are calling timer.server.resetTimer after $.connection.hub.start().done fires), you could do the following:

public void ResetTimer()
{
    SessionTimer timer;
    if (SessionTimer.Timers.TryGetValue(Context.ConnectionId, out timer))
    {
        timer.ResetTimer();
    }
    else
    {   
        SessionTimer.StartTimer(Context.ConnectionId);
    }
}

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.

public override Task OnReconnected()
{
    if (!SessionTimer.Timers.ContainsKey(Context.ConnectionId))
    {
        SessionTimer.StartTimer(Context.ConnectionId);
    }
    return base.OnReconnected();
}

You aren't ever calling SessionTimerHub.ResetTimer() in C# right?

查看更多
登录 后发表回答