Tuesday, January 8, 2008

A Session Data Management Tool

Introduction
In an ASP.NET application, it is very easy to pass session data from one web page to another. For example, the following code stores three pieces of data of an internet session using the .NET web session provided by the .NET framework:


...
Session("UserName") = sUserName
Session("PageName") = sPageName
Session("StatusCode") = nStatusCode


If later the user navigated to a different page, there is no need to pass the session data via the query string because they are already available on the server. The following code will retrieve the session data stored previously:


...
sUserName = CType(Session("UserName"), String)
sPreviousPageName = CType(Session("PageName"), String)
nPreviousStatusCode = CType(Session("StatusCode"), Integer)


By default, the session data is stored in memory of the server process, which makes it very efficient. If you want load balancing your applications using several servers or you want to be able to reboot the server machine quickly without big impact on current users, you can modify the web.config file of your application so that the session data will be stored in a SQL server database or in the memory of a Microsoft session state server instance.

It seems that we always want to do more than the current tool allows. For example, I would like to share session data among different applications. Some of my applications are running on machines without the .NET framework and some of them are not even web applications (windows services, console apps, and desktop GUI apps, etc.). Note that we are only dealing with applications we trust so there is no security issue here.

The .NET framework built-in session is not suitable for me because

It does not allow you to share session data among different applications. If you use the SQL server option, then it is possible to hack the stored procedures provided by Microsoft to allow session data sharing (but this is not an approach I like).
It is only available to .NET web applications, other applications cannot use it.
Using the SQL server option, session data is saved into a database. However there is no way for you to view the data outside of your application, because it is stored in binary format and you don't have access to Microsoft's source code.
In this article, I will introduce a simple, reliable, and efficient session tool that does not have these shortcomings. I could have any number of applications running on one or more servers all sharing the same session data. I hope this tool will be useful to you, too.

Overview of my session tool
The session data is managed by a .NET library Session.dll which contains the SessionManager class. The capability of sharing session data among applications is provided by a .NET web service called SessionService which uses the Session.dll component internally. This web service can also function as a .NET remoting server.

All session data is stored in the memory of the SessionService process. Theoretically the performance of the SessionService should be between the .NET in-memory session and the .NET SQL server session. However, the SessionService also saves session data into a local file periodically to prevent data loss due to server crash or reboot, etc.

Different applications can store and retrieve data from this SessionService. .NET applications can take advantages of the remoting capability to use more custom data types. Session data used by client programs won't be lost even if you reboot the server machine. Expired sessions will be cleaned up automatically. It is possible to set the timeout value for each individual session.

I have included with this article some sample code to access the SessionService. There is also a SessionBrowser program which can be used to view and change session data stored in the SessionService .

The SessionManager class
This is a class implemented in Session.dll written in VB.NET (actually it can be converted easily to any other .NET language in case VB is not your favorite choice). It implements the SetSessionData and GetSessionData methods

Public Function SetSessionData( _
ByVal sID As String, ByVal sKey As String, _
ByVal oValue As Object) As Boolean
Public Function GetSessionData( ByVal sID As String, _
ByVal sKey As String) As Object


The C# signatures are


public bool SetSessionData(String sID, _
String sKey, Object oValue);
Public Object GetSessionData(String sID, String sKey);


The sID parameter is a string that uniquely identifies a session, a guid string is a perfect candidate to be used as a session id. The sKey parameter must be a unique string within the corresponding session. As you can see, these two methods work with general .NET data objects (not just strings and integers). To store and retrieve session data, you do the following:


...
SetSessionData(sMySessionID, "UserName", sUserName)
SetSessionData(sMySessionID, "PageName", sPageName)
SetSessionData(sMySessionID, "StatusCode", nStatusCode)

...
sUserName = CType(GetSessionData(sMySessionID, _
"UserName"), String)
sPreviousPageName = CType(GetSessionData(sMySessionID, _
"PageName"), String)
nPreviousStatusCode = CType(GetSessionData(sMySessionID, _
"StatusCode"), Integer)


Here is the same code in C#


...
SetSessionData(sMySessionID, "UserName", sUserName);
SetSessionData(sMySessionID, "PageName", sPageName);
SetSessionData(sMySessionID, "StatusCode", nStatusCode);

...
sUserName = (String)GetSessionData(sMySessionID, "UserName");
sPreviousPageName = (String)GetSessionData(sMySessionID, "PageName");
nPreviousStatusCode = (int)GetSessionData(sMySessionID, "StatusCode");


Internally, the SessionManager class stores sessions in a shared (static for C#) hashtable. Each entry in the hashtable is itself a hashtable which contains data objects for a particular session. There is no need to create a session, it will be created when SetSessionData is called if the session does not exist already. The GetSessionData method will return Nothing (which is the same as null in C#) when the requested data does not exist.

Each session has a time stamp associated with it. When the SetSessionData and GetSessionData methods are called, the corresponding time stamp is updated. The session will expire if it has not been accessed for a specified amount of time. The SetTimeout method determines the expiration interval, the default is 30 minutes. When you call GetSessionData for an expired session, it will return Nothing as if the session was never created. The SetSessionTimeout method can be used to set the timeout interval for an individual session. The individual timeout interval always overrides the global timeout interval. Therefore you could have one session that expires in 10 years while the rest will expire in 30 minutes. The RemoveSession method is used to remove a session with given id.

The SaveSessions and LoadSessions methods are used to write all sessions to a file and read them from a file, they take one parameter that is the full path of a session data file . The SetSessionDataFile sets the path of the internal session data file. If you call the method OnStart at the beginning of your application, it will read session data from this internal session data file. The OnStart method will also launch a background thread that removes expired sessions to keep the memory usage from getting out of control. The OnStop method will save all current sessions to the internal session data file and kill the background thread. So if you put the calls to the OnStart and OnStop methods at the appropriate places in your code, you can shutdown and restart your app without losing any session data.

Note that if you want to persist session data to a file or you want to store and retrieve session data via the SessionService, all your session data objects have to be serializable.
If you have a serializable object implemented in your own class and you want to store it as session data using the SessionService, the dll that implements this class has to be copied into the Bin folder of the SessionService, otherwise serialization won't work properly.
All .NET applications, which do not have to be internet related, can use Session.dll within the same process as the application. The SessionManager class is derived from MarshalByRefObject, which means you can set up a .NET remoting server and access session data from various applications. Applications using the same .NET remoting server can access all session data hosted on that server, as long as the correct session id and session key are supplied when calling GetSessionData and SetSessionData .

To set up a remoting client, you need to create a configuration file containing the following text:






url=" http://localhost:80/SessionService/SessionManager.soap" />







The url parameter determines where the remoting server is located. At the beginning of your application, you need to have the following code

RemotingConfiguration.Configure(sConfigurationFilePath)

where sConfigurationFilePath is a string representing the full path of the configuration file. Then you can simply use the New method to instantiate a SessionManager object and call the SetSessionData and GetSessionData methods

...
Dim mgr As SessionManager = New SessionManager( )
mgr.SetSessionData(sSessionID, "TestData", "This is a test")
sData = CType(mgr.GetSessionData(sSessionID, "TestData"), String)

Here is the C# version

... SessionManager mgr = new
SessionManager(); mgr.SetSessionData(sSessionID, "TestData", "This is a
test"); sData = (String)mgr.GetSessionData(sSessionID, "TestData");

Note that once you configured the remoting client, session data will be saved to and loaded from the remoting server. Using the remoting server is not as efficient as keeping session data in memory of the same process, but it allows multiple .NET applications to share the same data. Also, using a .NET remoting server limits your session objects to serializable objects. We will talk about configuring the remoting server later. The disadvantage of using a .NET remoting server (comparing to using the web service) is that the clients can only be .NET applications.

Since multiple applications can access the same data via SessionManager , the actual data doesn't have to belong to any real user session. In fact, we can use this tool to read and write global data as demonstrated by the following code

...
Dim mgr As
SessionManager = New SessionManager( )mgr.SetSessionData("GlobalData", "RootDir", "c:\AllOfMyApps")
Dim sConn as String = CStr(mgr.GetSessionData("GlobalData", "ConnectString"))

Here is the C# version

...SessionManager mgr ;= new
SessionManager(); mgr.SetSessionData("GlobalData", "RootDir", "c:\AllOfMyApps");String sConn = (String)mgr.GetSessionData("GlobalData", "ConnectString");

The web service and the remoting server
The SessionService component provides the main features of the SessionManager class through a .NET web service. With this web service, you can have applications running on non .NET machines all sharing the same set of session data. You can even have applications accessing session data across firewalls.
To install the web service, you need to do the following

Unzip the files to the server machine. It must have the .NET framework and IIS installed.
Add a virtual web directory pointing to the SessionService subdirectory.
That's it. Now you can call the web service from client programs. Another way to install SessionService is run the included Install.vbs script.

The SessionService will call the OnStart method of SessionManager automatically when it is first accessed. The OnStop method of SessionManager will be called when the web service is shutdown. Therefore restarting IIS or even rebooting the machine will not cause any session data to be lost, unless there is an abnormal termination of IIS, of course.

For non .NET client programs, calling the SessionService is made easier by using XYSoapClient.dll. This is a regular com dll that can be used in C++, VB, and VB script to access almost any web service. For example, here is a script that set and get session data:

Dim obj
Set obj = CreateObject("XYSoapClient.1")
obj.InitService "http://localhost:80/SessionService/SessionService.asmx?wsdl"
If obj.InvokeMethod("SetSessionString", "MyID", "MyKey", "Test") Then
wscript.echo "Set session data ... Click OK to verify the data"
wscript.echo obj.InvokeMethod("GetSessionString", "MyID", "MyKey")
End If
Set obj = Nothing

The XYSoapClient.dll is described in detail in another article.

You can have multiple instances of the SessionService running on different machines or on the same machine. A limitation of the SessionService is that you can only use string data with non .NET clients. This is not a big deal because you can always represent other data types using xml, it is not possible to use .NET objects in a non .NET program anyway.

For .NET client programs. You can add web reference to access the SessionService. Alternatively, you can use the SessionProxy class. Both VB.NET and C# versions of this class are included with this article. Here is the VB.NET sample code

Dim proxy As SessionProxy
= New SessionProxy("http://localhost/SessionService/SessionService.asmx")
proxy.SetSessionData("MyID", "MyKey", "Test")
Dim sTestData As String = CStr(proxy.GetSessionData("MyID", "MyKey"))

And here is the C# version

SessionProxy proxy = new SessionProxy(
"http://localhost/SessionService/SessionService.asmx");
proxy.SetSessionData("MyID", "MyKey", "Test");
String sTestData = (String)proxy.GetSessionData("MyID", "MyKey");

If you look carefully, you will find the following text in the web.config file of SessionService:






type="SessionLib.SessionManager, Session"
objectUri="SessionManager.soap" />





The above text exposes the SessionManager object to .NET remoting clients. In other words, this web service is also a remoting server. Once you installed SessionService , you can have non .NET clients calling the web service while at the same time have .NET clients create SessionManager objects remotely. There is no need to set up a separate remoting server. Isn't that nice?

If you unzip the downloaded file to a folder and run the Install.vbs file, it will copy all the components to a folder of your choice and set up the web service and sample applications for you (creates virtual directories, etc.).

Error logging in the session service
The previous version of the SessionService writes error messages into the event viewer. The current version will create trace files in the Log folder. Information written into the trace file can be errors (level = 10), warnings (level = 20), or debug info (level = 30 or 40). You can configure the tracing capability of the SessionService from the web.config file.

Here is the section in the web.config file that determines how tracing is done.










The server will generate a new trace file each day. The TraceFilePrefix parameter determines where the trace files will be generated. The TraceLevel parameter determines how much information will be written to the trace file, the typical values are 0, 10, 20, 30, and 40 (0 means no tracing, 40 means the most tracing). The TraceCleanup parameter determines how long the trace files will be kept, the value 7 in the above means all trace files will be deleted automatically after 7 days.

Test programs and the SessionBrowser
The program SessionRemotingTest.exe and SessionConsoleTest.exe demonstrate how to access the SessionService via remoting from web and console applications. Some VB script files are included to demonstrate how to use the XYSoapClient.dll to access the SessionService.

The SessionBrowser program can be used to view data stored in the SessionService, it has to be able to access the session data file used by the SessionService. The configuration file of the SessionBrowser specifies the url of the SessionService and the full path of the session data file.



The Load Sessions button causes all session data from the SessionService to be loaded into the memory of the SessionBrowser. So the SessionBrowser will hold a snapshot of all session data after you click this button. You can refresh the data by clicking this button at any time.

You can selete the session you want to view by using the Session ID List drop down. Then you can use the Item Key List drop down to select a session object belonging to the selected session.

It is also possible to change session data from the SessionBrowser. If you modify the Session Data edit box and click the Set Data button, the modified data will be saved into the SessionService! Needless to say, you need to be very careful when using SessionBrowser to access production data.

To create a new session object in the SessionService, just enter a new session id string, a key string, and the data string, then click the Set Data button.

The Search Text button is used to find session data that contains certain strings. If you enter a string into the edit box to the left of the button and click the button, the session object that contains that string will be displayed in the Session Data edit box. If you click this button again, it will display the next session object that satisfies the search condition, etc.

There are other things you can do with the SessionBrowser. For example, you can extend or shorten an existing session's expiration time.

Typically, the SessionBrowser is used on the same server as the SessionService. It can be run on a different machine if the folder containing the session data file is shared. You need to change the SessionBrowser.config file in order to access a session service running on a different machine.

Finally, I need to emphasize here that the session web service described in this article should not be made available to untrusted applications. Thank you for reading my article. Please visit my home page for my other tools and articles.

Recent Updates

No comments: