Session provider for .NET Remoting and WCF
Project objectives
Create a session provider mechanism that mimics the ASP.NET Session. The provider target are distributed applications developed with VS.NET 2008 and .NET Framework 3.5. The Session object should be compatible with .NET Remoting and WCF technologies and must perform well in a multithreading environment. The Session provider should be customize by external components without recompilation.
Project background
The project was first developed on .NET Framework 2.0 and was used by a distributed application made with C# 2.0 and .NET Remoting. Now the application is run by over 2000 clients with good feedback, no bugs have been reported regarding the features that depend on the Session component. The reason behind the C# 3.0 upgraded version of the Session component is the fact that due to the multithreading environment the C# 2.0 version was using the ReaderWriterLock class. ReaderWriterLock has a lot of problems in environments with heavy load, .NET Framework 3.5 brings a new class named ReaderWriterLockSlim that improves speed and reduces the deadlock occurrence possibilities. You can find out more about the reasons why Microsoft decided to implement the ReaderWriterLockSlim from the Joe Duffy's Blog in this post and you can see a performance comparison of ReaderWriterLockSlim with ReaderWriterLock on Pedram Razaei's blog in this post. The older version of the Session project has allot of hard coded members, members that were application specific, the new version has to provide a way to add dynamic objects to the Session, objects that are not implemented inside the Session class.
Building interfaces
To reach one of the main objective(extensibility) I've created an interface for the objects that can register in the session instance. All objects that can register must implement the ISessionEntry. By this interface the Session provider can control the entries and in the same time the developer can create his own class that suits his needs without changing the Session class code.
Another interface used in the project is ISession. If you want to have your own implementation of the Session provider you can do it by inheriting ISession. Note that T is type of ISessionEntry and must have a default empty constructor.
public interface ISession<T> where T : ISessionEntry, new()
Building Session class
The full code of the Session class can be found here, but let me explain a little what's going on. The Session class is ensuring the thread safety of it's members using a ReaderWriterLockSlim instance called slimLoack. There are 3 collections inside that holds the objects managed by the class: activEntries, expiredEntries and data. The activEntries collection is most important one, to modify this List there 2 public methods (Register and Unregister) and one private method named ClearExpired that's called by the cleaner thread every minute. The expiredEntries is a private collection used by the cleaner thread inside ClearExpired and helps delete entries from the active collection.
public Session(int sessionTimeoutInMinutes)
{
slimLock = new ReaderWriterLockSlim();
cleaner = new Thread(new ParameterizedThreadStart(ClearExpired));
cleaner.IsBackground = true;
cleaner.Start(sessionTimeoutInMinutes);
}
As you can see in the constructor it can be set the timeout, there is no default value like in ASP.NET (20 minutes) because I think that any .NET Remoting server or WCF has it's own specific timeout, for example in the distributed application I mentioned the timeout is like 1h but if your application has per user licence then the timeout should be very short. In a per user licence case you should make on the client a KeepAlive Thread that every minute updates the LastAccessTime value on the server using the public method UpdateLastAccessTime. If the client shuts down without calling the Unregister method then after 1 minute he can login again because the cleaner thread has deleted the user entry from the active session.
The data collection is of type Dictionary<long, Hashtable> and mimics the ASP.NET way of storing and retrieving custom objects from the session. There is a Set and a Get method, in order to perform them the objects stored must be marked as serializable, if you'll use binary serialization in Remoting or netTcpBinding in WCF most of the objects will be suitable for session storage.
There is a method named PrepareForDispose, it's not a common method for a .NET class but I wanted to make sure that the cleaner thread will exist gracefully, calling Abort on a thread is very nasty way to exit because it will throw a ThreadAbortException. So my PrepareForDispose method wakes up the thread if it's in sleep mode and waits for it to finish processing like this:
//set volatile flag
running = false;
//wake up cleaner
if (cleaner.ThreadState == ThreadState.WaitSleepJoin)
{
cleaner.Interrupt();
}
//wait for the thread to stop
for (int i = 0; i < 100; i++)
{
if (cleaner == null || cleaner.ThreadState == ThreadState.Stopped)
{
System.Diagnostics.Debug.WriteLine(
"Cleaner has stopped after " + i * 100 + " milliseconds");
break;
}
Thread.Sleep(100);
}
Using the Session provider
Before using the the Session provider we have to create a class suitable for a session entry, the most common usage is the User class.
[Serializable]
public sealed class User : ISessionEntry
{
#region ISessionEntry Members
public long SessionId
{ get; set; }
public DateTime LastAccessTime
{ get; set; }
#endregion
public string Username
{ get; set; }
public string Password
{ get; set; }
}
The following example is a console application and it shows how the methods of Session class can be used:
class Program
{
static void Main(string[] args)
{
//Session object creation
Session<User> sessionManager = new Session<User>(1);
//Register timeout event
sessionManager.OnEntryTimeout +=
new SessionEntryTimeoutDelegate<User>(sessionManager_OnEntryTimeout);
//New user
User user = new User() { Username = "Stefan", Password = "Prodan" };
//Login
sessionManager.Register(ref user);
//Call Session specific methods
Console.WriteLine("User {0} session id is {1}",
sessionManager[user.SessionId].Username, user.SessionId);
//Store on the session a user's object
sessionManager.SetData("MyData", "UserObject", user.SessionId);
Console.WriteLine("{0} session data is {1}",
user.Username, sessionManager.GetData("MyData", user.SessionId));
//Wait for the user session to expire
Console.WriteLine("Wait until the session expires (1 minute)");
Console.ReadKey();
//Free resources used by the session
sessionManager.PrepareForDispose();
Console.ReadKey();
}
static void sessionManager_OnEntryTimeout(User user)
{
Console.WriteLine("Session expired for user id {0}", user.SessionId);
}
}
In the future I will post real examples of how to use Session provider in production with .NET Remoting and WCF. Full source code of the examples will be uploaded to my SkyDrive public folder so anyone can get them. In the meanwhile I am expecting comments and ideas about improving the session provider.
