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 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
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.
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.
The Reconnected Function
The Reconnected Function, for us, does the same thing as the connected function, it calls the UpdateCache method in our ConectionCache.
It also calls the joined method on the client side passing along the connectionName and the current time stamp.
Client Side Hub
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.
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 cache is a simple Dictionary of UserCredential objects. The UserCredential entity encapsulates the UserId, ConnectionStatus (an enum) and a list of ConnectionSession entities.
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.
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.
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.
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.
It also has three helper methods CreateNewUserSession, UpdateUserSession and ExpireSession.
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.
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.
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.
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
The Index Page
We remove the defaults ASP.NET markup and replace it with the following
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 _Layout Page
We add references to the SignalR and SSTimer bundles in the _Layout page as follows
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.
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.
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.
Putting it all together
Once the code is all in place, we run the application. Initially it looks as follows
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.
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’
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.
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