My team has developed several Line of Business applications using Silverlight. Most but not all have at least 3 if not all of the following feature requests:
- Allow users to be signed into the LOB more than once. Users like having more than 1 browser tab or even more than 1 browser type with the LOB app loaded simultaneously.
- Ability to differentiate between a user in Tab1 versus Tab2 for purposes of concurrency, etc.
- Limit the number of simultaneous sessions a specific user can have. 1 is a valid constraint.
- Automatically log out a user instance after X seconds a.k.a session time out
- Gather user information such as browser type/version, screen resolution, IP, etc
- # of concurrent distinct users logged in over time.
- # of concurrent users logged in over time total.
- Max or average simultaneous sessions by users.
All of these are good feature requests for any Line of Business Application. Thinking through it, these features share an implementation detail that requires the LOB to track each signed in user instance.
Here was the plan.
- Create a new database table called AppInstance. This table will contain a list of all the active users in the system.
- Create a new database table called ArchivedAppInstance. This table contains all previously logged in users.
- Whenever a user logs in, add a row to the AppInstance table that records the UserId, timestamp of when that user logged in and any other information your LOB might want to know.
- Whenever a user logs out, archive the #3 AppInstance row to ArchivedAppInstance table noting the logged out timestamp.
The information in these 2 new tables enable the implementation of the features above. Conceptually this is very simple. In fact, 1 through 3 above are easy. #4 it turns out, in Silverlight was not as straight forward as one might think. Why? First, there are multiple ways the application can be exited such as clicking the LOB “Log Out” button, closing the browser directly, navigating away from the LOB web page or, heaven forbid, a browser crash. Except for crashing, the Silverlight application’s Exit event is raised but if you try to perform some backend action like calling a web service it won’t go through since the thread required to make the service call itself is gone.
I worked on a team to solve this. My colleague Tammy McClellan recently documented our approach for other product owners internally and I thought I would share on my blog. In addition, Tammy created an extremely small and simple sample application with our implementation. You can find the code here.
Example App Explanation
Running the sample app will show 2 buttons. “Insert AppInstance, Delete using WebService” and “Insert AppInstance, Delete using WCF”. Think of the buttons as logging into your Silverlight LOB application. Clicking a button adds a row to the AppInstance table that records the UserId, timestamp of when that user logged in, screen resolution, IP, browser type etc.
Insert AppInstance, Delete using WCF Button
The sample app uses Entity Framework and creating the AppInstance is pretty standard code. I won’t go into much detail, please see the code as needed. The key here is setting the App.OnExitUseWCF to true.
Insert AppInstance, Delete using WebService button
The key here is setting the App.OnExitUseWCF to false.
Shutting Down the Application
Shutting down the sample application will trigger the Silverlight Application_Exit event shown below.
Depending on which button you used, will determine the technique used to archive the AppInstance. If you use the “Insert AppInstance, Delete using WebService” button, the AppInstance is correctly archived. If you use the “Insert AppInstance, Delete using WCF” button the AppInstance does NOT get archived AND no error is thrown. The call to the WCF service silently killed.
Please check out the XmlHttpRequest.cs class in the source code.