Server Side Timer in an ASP.NET MVC application using SignalR

Posted by: Sumit Maitra , on 8/6/2012, in Category ASP.NET MVC
Views: 94667
Abstract: SignalR, as we all know, is a persistent connection framework in .NET. We have seen how easy it is to set it up for broadcasting messages to clients. Today we will see a few more features namely Connect, Disconnect and Reconnect that enable us fine grained control over client connection.

While working on a prototype recently, I came across the need to build a timer on the server side that could track how much time a person spent between from login to logout on an ASP.NET MVC web application. Another requirement was to be able to display a timer on the client representing how long the person was logged in. Finally it was required that only one browser window be active at a time. Hence if the user logged in with same credentials on multiple browsers, the system should take inputs only from the last logged in browser. To implement these features,  I chose SignalR to establish persistent connections between the logged in user and the server and then wrote a small cache to store the session information. How much of the cache should be persisted and at what point was left open.

 

If you are new to SignalR, read ASP.NET MVC 3 Real Time Collaborative Apps with SignalR

The Hub

The Server Side

The Hub infrastructure in SignalR doesn’t automatically handle connect and disconnect events. To manage this, you need to implement the IDisconnect and IConnected interfaces provide by the SignalR.Hub namespace.

The IDisconnect interface has one method Disconnect() whereas the IConnected interface has Connect () and Reconnect().

Hubs, as we know, have the server side methods that can be called from the clients. However the IDisconnect interface implementation are called from the Server side and as a result, they don’t have the SignalR Hub Context object fully populated. The only value available is the ConnectionId.

The skeleton of the ServerSideTimeHub looks as follows

server-side-timer-hub-skeleton

The Connect method is called by the client every time the user does a PostBack. This call is made by the SignalR Hub framework. No additional calls apart from the hub initialization (that we’ll see shortly) is required. The Disconnect method is called when the user navigates away from the page by clicking a link, doing a postback etc. The SignalR Hub broadcasts the detected disconnects. Disconnects are also triggered on timeouts. Details of how to change configuration can be found on the SignalR Wiki.

Server Side Implementation

The Connect Function

The Connect Function retrieves the connectionID from the SignalR Hub’s Context property, checks if the User is authenticated, and if authenticated, calls the UpdateCache method in our ConnectionCache. It also raises the client side event joined for the particular client. As seen in the code below, we can target a particular client by using the connectionId indexer. To broadcast to all clients, we call the client side method on the Clients object without any indexer.

The connectionName has the User Name when the user is authenticated and the ConnectionId when user is not authenticated.

server-side-timer-hub-connect

clip_image001
The Connect method simply sends back the connectionName and the current date time to the client.

The Disconnected Function

The Disconnected function only has the connectionId with it. It calls the ConnectionCache’s Disconnect method and then raises the client side event - leave.

server-side-timer-hub-disconnect

The Reconnected Function

The Reconnected Function, for us, does the same thing as the connected function, it calls the UpdateCache method in our ConectionCache.

server-side-timer-hub-ewconnect

It also calls the joined method on the client side passing along the connectionName and the current time stamp.

Client Side Hub

Since we are working on ASP.NET MVC, we will use the excellent JavaScript client side libraries for SignalR. The client side hub only needs the following two lines of code to be initialized.

sstimer-base-hub-init

 

Client Hub Implementation

The client side hub has the implementation of the functions called from the server, viz. join, leave, rejoin and tick (tick method is called from inside the Connection Cache as we will see later in the article).

The method implementations code is shown below. Each method updates one or more of the following labels on screen Status, Connection Id, User Id and Online Since (the ticking timer).

We already saw how the joined, rejoined and leave methods are called from the server. The tick method is called every second and represents a ticking timer that refreshes approximately once a second. It displays the time interval passed since log in.

sstimer-base-hub-functions

Building the Connection Cache

The cache is a simple Singleton that uses Fourth version from Jon Skeet’s seminal article on how to implement Singleton Pattern.

The Model

The cache is a simple Dictionary of UserCredential objects. The UserCredential entity encapsulates the UserId, ConnectionStatus (an enum) and a list of ConnectionSession entities.

model-user-credential

ConnectionSession objects store the Connected time and Disconnected time as reported by SignalR for a particular user. ConnectionId is the same as reported by SignalR. So after a user Logs in, every connect event fired adds a ConnectionSession object to the UserCredential.

model-connection-session

There is one UserCredential maintained per user login for a given user Id. So if a user logs in from two browsers using the same user id, they will be logged as different ConnectionSessions for the same user. When the user logs out, the UserCredential is purged from the Cache (this would be a good place to persist it to the database).

The UserCredential entity also has a helper method that gets the length of the current session in Ticks. To do this, the GetSessionLengthInTicks method iterates through all the ConnectionSession objects and sums up the difference of Disconnected Time to Connected Time. If the DisconnectedTime for the last entry is 0, it takes the current date time value.

The Cache

For the prototype, the cache implementation is the heart, as it is responsible for managing connection and their states. The cache also emits a ping as soon as a user is added to it, and continues to emit the ping every second until all the users have logged out.

The Ping Timer

The Ping Timer is implemented with help of a Timer object from System.Timers namespace. The Timer is initialized in the constructor and set to Tick every 1 second. It is set to automatically reset itself after the interval has elapsed.

cache-ping-timer

A Timer elapsed event is handled by the _timer_Elapsed event that calls the PingClients method.

The Ping Clients method uses the SignalR GlobalHost to retrieve the correct Hub context. Thereafter we loop through all the clients in our _connections dictionary and fire the tick method for each logged in user. The SignalR Connection Id, Logged in User Id and time elapsed is passed to the tick method.

Cache Management

It also has three helper methods CreateNewUserSession, UpdateUserSession and ExpireSession.

cache-management-collapsed

UpdateCache: The UpdateCache method is invoked by the SignalR connect and reconnect events from the Hub. Based on whether the user exists in cache or not, the UpdateCache method either calls the CreateNewUserSession method to create the new user or the UpdateUserSession to update the existing user. If the user already exists its current session is ‘expired’ by updating the DisconnectedTime property.

cache-management-update-cache

The CreateNewSession method is also responsible for starting the timer if the cache is empty before adding the new user.

Disconnect: The Disconnect method is invoked by the SignalR disconnected method. It simply marks the current session of the user as complete but putting updating DisconnectedTime property of the
appropriate ConnectionSession object.

cache-management-disconnect

Logout: The Logout method is invoked directly from the AccountController method when user explicitly clicks Logout. Logout first Expires the current session and then removes UserCredential from the cache.

cache-management-logout

That’s pretty much all of the cache implementation. All we now have are the View layer changes that we will see in the next session.

The View Layer

We are using the Internet template of an MVC4 project type. This gives us the Account Management functionality for free. Since we don’t need anything els,e we keep the Index.cshtml and delete the About and Contact pages. We rollup our JavaScript in one file called sstimer-base.js. We have some ajax related utility methods in the sstimer-ajax.js file.

The Index Page

We remove the defaults ASP.NET markup and replace it with the following

view-index-chstml


We essentially have four labels indicating status of the client’s connection to the server.

  • Status – Shows Joined, Left or Rejoined
  • Connection ID – Shows the GUID used by SignalR. This can be removed from the final implementation.
  • User ID – The User logged in
  • Online Since – Label indicating how long a user has been logged in (hh:mm:ss.ssss format).

The div with class id ss-wizard encapsulates a dummy multi-page form that user can navigate by clicking Previous and Next buttons. On Click, the button makes an AJAX postback and retrieves some JSON Data. The relevant JavaScript for this is also defined in the sstimer-base.js file.

The _Layout Page

We add references to the SignalR and SSTimer bundles in the _Layout page as follows

view-layout-chstml


Note the addition of the /SignalR/Hubs script. This is generated dynamically at runtime by the SignalR framework.

Registering the Bundles

In the RegisterBundles method in the App_Start/BundleConfig class is updated to add a bundle for SignalR and another one for Server Side Timer.

view-register-bundles

The Home Controller

The Home Controller has minor cosmetic changes to the message. We also add a new Action method that is called by the Previous/Next button of the Wizard. In a real life example it will be passing back more data, for the prototype we simple lob the page number back as a JsonResult.

view-home-controller

The Account Controller

We have one small change in the account controller to call the Cache’s Logout method before logging the user out from the system.

view-account-controller

Putting it all together

Once the code is all in place, we run the application. Initially it looks as follows

ui-not-logged-in

As we can see, SignalR initiated a connection but there are no user currently, hence “Online Since:” is also empty. Now lets click on Log in and login to the system.

ui-logged-in

We see the Status has changed, the User Id has been populated and the Online Since timer has started ticking. We can click Previous and Next and see how the “Data from the Server:” label’s output changes.

Now if we start a new browser and log in using the same user, we will find that this user’s timer stops ticking and the ‘Previous’/’Next’ button get disabled and the Status changes to ‘Left’

ui-logged-in-twice

Now if we Log off and log back in, the timer will be reset. However if we close the browsers and come back in 20 seconds, the timer will continue where it left off.

Conclusion

We used SignalR in a scenario where we needed to track client engagement period. We also saw how the Connect, Disconnect and Reconnect mechanism works in SignalR. SignalR though a popular platform for building chat and two way communication applications, can also be used for directional control of a user session.

Code and Demo

The entire source of this application can be downloaded over here. You can fork it on Github here

Here is the Live Demo

Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles
Sumit is a .NET consultant and has been working on Microsoft Technologies since his college days. He edits, he codes and he manages content when at work. C# is his first love, but he is often seen flirting with Java and Objective C. You can follow him on twitter at @sumitkm or email him at sumitkm [at] gmail


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by Egenes on Monday, August 6, 2012 7:59 AM
Brilliant. However I am unable to wrap my head around a practical use of creating a server side timer. Can you give some examples
Comment posted by Sumit on Monday, August 6, 2012 12:43 PM
Hi Egenes,
Timer was a visual example; essentially having a cue on user engagement can be useful in multiple ways of gauging a survey effectiveness, or tracking user engagement on a site to better understand usability bottlenecks etc. We could ignore the tick at the client end and simply make sure we persist each client round trip to see how much time one is spending at a page/functionality of a web application. The persistent connection and timer is means to collect data that will further other ends mostly analytical.
Comment posted by Egenes on Monday, August 6, 2012 12:56 PM
Sounds like a good use case! Thanks
Comment posted by Flutz on Wednesday, August 8, 2012 2:43 AM
I am new to SignalR and this and your previous article has helped me a great deal to understand the concepts. Has SignalR been officially included in the ASP.NET platform? Why is their a need of both a server and a client side library?
Comment posted by Sumit on Wednesday, August 8, 2012 3:45 AM
Hello Flutz,
By official support if you mean being able to talk to Microsoft Support for SIgnalR issues, then the answer is not yet. It is coming though. I confirmed over twitter with David Fowler (co-author of SignalR). If you are on twitter the conversation is here https://twitter.com/sumitkm/status/233113375536713728

Having said that the SignalR community offer tremendous support over twitter and also at the SignalR chat room at http://www.jabbr.net/

Thanks,
Sumit.
Comment posted by Moore on Monday, August 13, 2012 10:18 PM
How do I stop polling the server in SignalR if an error occurs? I have tried
connection.hub.stop() but it looks like a lame solution.
Comment posted by Louis on Thursday, September 6, 2012 9:32 AM
This is a nice demo and I respect that you took the time to create it and share it.  We've been evaluating SignalR for use at a financial services company where we have a need to push real time market prices (among other things) to connected web browser clients (among others).  Our load testing against an internal SignalR hub revealed a steady increase in browser (IE 8-9, Chrome, FF, Safari) memory over time. We got the lowest rate of increase when we forced long polling as the transport. It is still early in the process for us and we are trying to isolate the cause of the memory increase. In our test scenario, we aren't adding DOM elements and in fact we see the memory increase even when we don't display anything on the UI. I was anxious to see if perhaps your demo revealed a way around the memory issue.  Unfortunately, I'm seeing the memory increase in your demo as well.  If after registering and logging in, the browser memory will steadily increase.  Our IE browser memory growth rate at one message a second is ~8MB / hour which looks to be about the same rate as your demo.  I doubt the memory increase is a deal killer for most folk’s use cases but in our case we need the browser up on user’s monitors 24x7 for at least a week during which time their browsers could receive hundreds of thousands of messages each from the SignalR hub.   The rate of memory increase at those volumes over seven days is untenable.  Machines get bounced once a week which is the reason for the 7 day time frame.  Our use case is addressed via desktop apps at the moment but we'd really prefer to transition everything to the web.   Any help or guidance on how to address the browser memory increase would be genuinely appreciated.
Comment posted by Louis on Thursday, September 6, 2012 12:17 PM
Sorry about the three duplicate posts.  Unsure why that occurerd but it was unintentional.
Comment posted by Sumit on Friday, September 7, 2012 6:52 AM
Hello Louis,
Thanks for pointing this out. I will try to see if I can pull something up on my side.

Did you guys report this to the SignalR team on Github? I'll do some digging around there too.

Thanks and Regards,
Sumit.
Comment posted by Marsh on Wednesday, October 3, 2012 3:07 AM
Thanks for your great work. This is exactly the thing I was looking for and started to implement and came across your article. Its brilliant work and I respect your time to put all this on the blog. Just wondering is there any performance hit when we update the cache on each reconnect, connect? How do I go ahead stress test the signalR impleemntation.

Comment posted by Sumit on Wednesday, October 3, 2012 12:02 PM
Hello Marsh,
Glad you found the article useful. SignalR performance has been in serious discussions all around. I haven't gotten around to doing a stress test myself. But I don't think I can hold off much longer.
Till such time I encourage you to look at SignalR's GitHub repository and adapt some of their performance test cases to your needs.

I'll check myself and post any relevant links I find too.
Comment posted by Marsh on Thursday, October 4, 2012 6:19 AM
Thanks. I have a query, may be you can help me with. I have a js file which is in the master page of my website. That js file is basically manages the hub connection for signalR.I am not sure about signalr connection behaviour on every page load. I noticed that even if i refresh the same page,it fires the connect event on the server side, but doesnt fire the disconnect event. is that true? that way I am still adding many connections to my user mapping entity.
Comment posted by Marsh on Thursday, October 4, 2012 6:20 AM
By the way thanks for the reply! Good Work! :)
Comment posted by Marsh on Thursday, October 4, 2012 6:39 AM
By the way thanks for the reply! Good Work! :)
Comment posted by Marsh on Thursday, October 4, 2012 6:46 AM
By the way thanks for the reply! Good Work! :)
Comment posted by Marsh on Thursday, October 4, 2012 6:53 AM
By the way thanks for the reply! Good Work! :)
Comment posted by Samantha on Monday, October 29, 2012 9:37 AM
Can I use the above cache implementation if my signalr server is going to use redis as a backplane for loadbalancing?
Comment posted by Sumit on Monday, October 29, 2012 3:09 PM
@Samantha,
I haven't explored SignalR load balancing with Redis. But looking at it simply, the cache is hosted by ASP.NET process. The moment you have two or more servers serving the same request, the cache is no longer 'singleton'.

I am curious to know why would you need another cache implementation when you have a Redis backplane. Couldn't redis serve as your cache store? (I am a relative newbie to Redis so please pardon my naivete)

Thanks and Regards,
Sumit.
Comment posted by GABRIEL ROGERS on Sunday, November 4, 2012 1:33 AM
i have tried to connect net timer but finally failed any help welcome
Comment posted by Seba on Friday, November 30, 2012 12:53 AM
Many thanks for this, Sumit.
Are you're able to tell us what to change to run it with the current SignalR 1.0 alpha?
Comment posted by Sumit on Wednesday, December 12, 2012 8:08 AM
Hello Seba,

There are some breaking changes with SignalR 1.0 alpha. I'll try to do a followup with 1.0 as soon as possible.

Thanks and Regards,
Sumit.
Comment posted by aaaa on Friday, December 28, 2012 12:19 AM
hai
Comment posted by Scott on Friday, January 11, 2013 10:01 AM
Great demo!  Do you have a modified version with the 1.0 SignalR release?  I do not see the IDisconnect interface in the 1.0 release.
Thanks
Comment posted by Sumit on Thursday, February 14, 2013 8:35 AM
As of Alpha 1.0 following are the changes:
1. Nuget Package name is Microsoft.AspNet.SignalR
2. You no longer need to implement IConnect and IDisconnected. Instead override the OnConnected, OnDisconnected, OnReconnected in Hub.
Theoretically that is all. I'll try it out and post my results.
Thanks and Regards,
Sumit.
Comment posted by Vyom Sharma on Tuesday, May 6, 2014 7:55 AM
Hi Sumit,

I had implemented the chatting module in my web site using SignalR. My problem is i am not able to reconnect my client when it Lost connection by ethernet cable disconnection or any other reason even my client disconnected event is not fired when client lost the connection. can you please provide me any article or link or example  that clarifies my doubts regarding this connection lifecycle or help me to learn to maintain connection....
Comment posted by sarveshwar on Monday, June 9, 2014 9:55 AM
I am creating a similar timer application using signal r. I installed Microsoft.AspNet.SignalR.2.0.3 package from nuget. But I am not getting interfaces IConnected and IDisconnect in this package. Any alternate solution present or not. Thanks in advance.
Comment posted by sarveshwar on Monday, June 9, 2014 11:52 AM
I am creating a similar timer application using signal r. I installed Microsoft.AspNet.SignalR.2.0.3 package from nuget. But I am not getting interfaces IConnected and IDisconnect in this package. Any alternate solution present or not. Thanks in advance.

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel