Monday 28 November 2011

How to share data among your .NET applications

Introduction

Suppose you have several different web applications that require users to login. It would be nice that users login only once and be able to move across different applications without logging in again. It would also be nice that once users make some choices in one application, they would apply to other applications as well. In other words, users would not know that they were using different web applications. Google is a good example: if you login to Google Voice, you can navigate to GMail without logging in again. There are probably many ways to accomplish this, one of them is to share session data among your applications. After you login to one application, your session will contain your information including the fact that you have successfully logged in. When going to a different application (by clicking a link in the current application, for example), the second application will fetch your current session data and the data will be used to determine whether you have already logged in. Besides user sessions, there are other types of data that you may want to share among your applications, such as common application settings, user preferences, user browsing history, the products that have been viewed or selected, etc.
Let's focus on sessions now. How do you share user session data among different web applications? With built-in session management in ASP.NET, there are three different modes you can choose from: in-proc (in memory), state server, and SQL Server. All of them do not allow you to share session data among different applications. It is possible to hack the ASP.NET session database to achieve the goal of sharing session data, this is not what I want to discuss in this article, however.
I wrote and published an in-memory session management tool on CodeProject a long time ago, it allowed sharing of session data. In the following sections, I am going to introduce a new database based solution that can be used to share data (including session data, of course) among different .NET applications. As you can see, it is simple and requires minimal coding to use, and it is not limited to web applications.

A New Database Session Tool

Here is what you need to do to install and use this solution (all files are included in the zip download):
  1. Create a new empty SQL Server database called SharedSessions.
  2. Run the SQL script file SharedSessions.sql in the new database to create tables and Stored Procedures.
  3. Create a user (login) for this new database, to be used by your applications, and grant it privileges to execute all Stored Procedures.
  4. Add a reference to SessionService.dll in your .NET applications and use it to create / delete session and store / retrieve session data.
First, let's see what is session in this article. Session is a collection of .NET objects, called data items, that you want to group together and store in the session database, it does not have to be related to any user session. Each session is uniquely identified by a session ID string. Each data item within a session is uniquely identified by an item key string. There is no restriction in my solution on session ID string and item key string besides database field length: for session ID, the maximal length is 100, for item key, the maximal length is 300. A session has an integer timeout value which is the number of minutes after which the session will expire: if the timeout value for a session is 30 and your applications do not read or write any data in this session for over 30 minutes, then the session is expired and data in the session can no longer be accessed.
By looking at the database created in the above using SQL Server Management Studio, you can see that there are only two tables: Session and SessionData.
The Session table stores information about session, there are four fields: SessionID, Timeout, DateTimeCreated, and LastAccess. The SessionData table stores session data items, there are three fields: SessionID, ItemKey, and ItemText. ItemKey is a text field that holds the actual data value, a Base64 encoding of .NET object.
Besides the two tables, there are seven Stored Procedures in the SharedSessions database.

  1. BrowseSessions returns database records that match the given session ID, item key, start date, and end date.
  2. CreateSession will create a new session in the database with the given session ID; if a session with the given ID already exists, then the session will be cleaned up (all data items in the existing session will be deleted) and the timeout value reset.
  3. GetSessionData returns the session data item with the given session ID and item key.
  4. RemoveSession will delete the session with the given session ID. If no session ID is provided, it will delete all expired sessions.
  5. RemoveSessionData will delete the session data item with the given session ID and item key.
  6. SetSessionData will insert / update a session data item in the database. If a session with the given session ID does not exist, it will be created first using the default timeout value (30 minutes).
  7. SetSessionTimeout will reset the timeout value for a session. If a session with the given session ID does not exist, it will be created.
SessionService.dll is a .NET library that can be used in any .NET application (not limited to web applications) to read from / write to the SharedSessions database. It contains one class, Session, with the following public static methods; all of them access the SharedSessions database by calling the Stored Procedures listed above:
  • StartCleanupProcess: This method starts a background thread to periodically delete expired sessions from the SharedSessions database.
  • CreateSession: It creates a new session.
  • SetSessionTimeout: It resets the timeout value for a given session. It will create a new session if there is none with the given session ID.
  • SetSessionData: It is used to insert / update a session data item. It will create a new session if there is none with the given session ID.
  • GetSessionData: It retrieves a session data item. If the data item does exist or the session has expired, it will return null.
  • RemoveSession: It deletes the session with the given session ID or deletes all expired sessions if no session ID is given.
  • RemoveSessionData: It deletes the session data item with the given session ID and item key.
  • SetAppSetting: To be explained later.
  • GetAppSetting: To be explained later.
  • ExecuteSQL: Returns a data set that is the output of a SQL query on the SharedSessions database.
In order to use SessionService.dll, your .NET application needs to reference it and have a database connection string for SharedSessions defined in the AppSetting "SessionDBConnection" in the application configuration file, or web.config file if it is a web application. Note that three methods can cause a new session to be created: CreateSession, SetSessionTimeout, and SetSessionData.

Types of session data items

So what kind of data can be saved in the SharedSessions database? Any .NET data object that is serializable can be saved into and retrieved from the database. In particular, you can save an object array as a session data item as long as each object in the array is a serializable object or an array of serializable objects. This gives us a lot of possibilities. See the following code example.
string SessionID = "my session id 123";
string ItemKey = "my item key 456";
object[] ItemValue = new object[3];
ItemValue[0] = "Hello, world";
ItemValue[1] = DateTime.Now;
string[] StateList = {"California", "Alaska", 
         "Washington", "Virginia"};
ItemValue[2] = StateList;
// store ItemValue into session database
SessionService.Session.SetSessionData(SessionID, ItemKey, ItemValue);
// retrieve ItemValue from session database
object[] ItemValueOutput = 
  (object[])SessionService.Session.GetSessionData(SessionID, ItemKey);
// print value of ItemValueOutput
System.Console.Out.WriteLine(ItemValueOutput[0].ToString());
System.Console.Out.WriteLine(ItemValueOutput[1].ToString);
string[] StateListOutput = (string[])ItemValueOutput[2];
for(int i=0; i<StateListOutput.Length; i++)
    System.Console.Out.WriteLine(StateListOutput[i]);
In the above code, ItemValue is an object array of length 3. The first item in the array is the string "Hello, world", the second item is the current date time, the third item is a string array of four state names. Such an object is stored into the SharedSessions database with only one call to SetSessionData and retrieved from the database by calling GetSessionData. Internally, the ItemValue object is serialized into a byte array and then encoded as ASCII text using the Base64 scheme. The Base64 text is what is stored as the ItemText field in the SessionData table. Here is the code in SessionService.dll that encodes an (serializable) object into text and decodes the text back to an object:
private static string ObjectToBase64(object obj)
{
    if (obj == null) return null;
    BinaryFormatter formatter = new BinaryFormatter();
    MemoryStream stream = new MemoryStream(64*1024);
    formatter.Serialize(stream, obj);
    byte[] output = stream.ToArray();
    return Convert.ToBase64String(output);
}

private static object Base64ToObject(string input)
{
    if (input == null || input == "") return null;
    byte[] data = Convert.FromBase64String(input);
    if (data == null) return null;
    BinaryFormatter formatter = new BinaryFormatter();
    MemoryStream stream = new MemoryStream(data);
    return formatter.Deserialize(stream);
}

Going beyond sessions

As I mentioned before, this solution is not limited to user sessions. It can be used to share other kinds of data among .NET applications. For example, you have a common dropdown list in several of your applications. The data items in the dropdown list can be stored in each application's configuration file, a better way is to store the data items in the SharedSessions database.
// store list items into database once
string AppSettingCategory = "All lists in my applications";
string AppSettingName = "How to kill myself option list";
object[] ItemList = new object[3];
ItemList[0] = new string[2]{"0", "Shoot myself with a gun"};
ItemList[1] = new string[2]{"1", "Listen to a boring project presentation"};
ItemList[2] = new string[2]{"2", "Use Lotus Notes"};
SessionService.Session.SetAppSetting(AppSettingCategory, AppSettingName, ItemList);

...
// retrieve and use list items from database in multiple applications
object[] ItemListOutput = 
  (object[])SessionService.Session.GeAppSetting(
   AppSettingCategory, AppSettingName);
for(int i=0; i<ItemListOutput.Length; i++)
{
    string[] Item = (string[])ItemListOutput[i];
    string ItemValue = Item[0];
    string ItemText = Item[1];
    System.Console.Out.WriteLine("Value: " + ItemValue + ", Text: " + ItemText);
}
Note that we use SetAppSetting and GetAppSetting instead of SetSessionData and GetSessionData. The difference is, with SetAppSetting, an internal session is created to store application data (the internal session will always have the string "__@@##" as the prefix of its session ID) and the timeout value of the internal session is 30 years instead of 30 minutes, so it will "never" expire!
This is great. The SharedSessions database is not just used for user sessions any more, you can save all kinds of useful application data and use the saved data in all of your .NET applications! But there is a flaw, a serious one. Let's say you save a list of items for a lot of common dropdown lists into SharedSessions, three months form now, how do you know what data has been saved for each dropdown list? Remember, the ItemText field in the SessionData table is a Base64 representation of a serializable .NET object, it will never be obvious what data object it represents. What if you want to make modifications to the data saved in SharedSessions? Do you need to write code to deserialize the data first and then print everything out to the console, if you forget what has been saved? Don't worry, I have another tool to solve this problem. It will be discussed in the next section.

SessionBrowser.exe, a tool to view and modify your data

SessionBrowser.exe is a simple Windows Forms application that uses SessionService.dll to access the SharedSessions database. It provides an easy way for you to view and modify the data you saved into the database.
How do you find your data using SessionBrowser.exe? If you know the session ID, type it into the "Session ID" text box and click the "Select Sessions" button. All sessions matching your session ID (you do not need to type the full session ID, by the way) will be displayed into the list view "Session List". The list view "Session List" will display up to 200 most active sessions that were created between "Start Date" and "End Date" and these sessions will be arranged in descending order of last access time. If your data is not found, then maybe your session was not created in the date range; in that case, you need to change the "Start Date" and/or "End Date" and click the "Select Sessions" button again.
When you select a session in the list view "Session List", up to 200 session data items for that selected session will be retrieved and displayed in the list view "Session Item List". If you select a session item from "Session Item List", an XML representation of the selected session data item will be displayed in the text box "Session Item Selected". You can modify the XML text and click the "Update Selected Item" button, and the selected session data item will be updated in the database!
Now let's look at each UI control on the application window.
  1. "Database" text box: This is where the "SessionDBConnection" setting in the configuration file is displayed. You can modify it to access a different database.
  2. "Session ID" text box: This is used when finding existing sessions and creating a new session and creating a new session item.
  3. "Item Key" text box: This is used when finding existing session items and creating a new session item.
  4. "Start Date" and "End Date" text boxes: These are used to limit sessions displayed in the list view "Session List" when the "Select Sessions" button is clicked.
  5. "Select Sessions" button: It uses the text boxes "Session ID", "Start Date", and "End Date" to find sessions in the database and display them in list view "Session List".
  6. "Select Session Items" button: It uses text boxes "Session ID", "Item Key" to find session items in the database and display them in the list view "Session Item List".
  7. "Clear All" button: It clears everything on the screen except the "Database" text box.
  8. "Session List" list view: When a session in this list view is selected, the data items in this session will be displayed in the list view "Session Item List".
  9. "Session Item List" list view: When a session item in this list view is selected, the session item data will be displayed in the text box "Session Item Selected" in XML format.
  10. "Session Item Selected" text box: It displays the XML representation of the selected session data item from the list view "Session Item List". You can use this text box to modify the selected session data item.
  11. "Timeout" text box: This is the timeout value for the new session when you click the "Create Session" button.
  12. "Create Session" button: Clicking this button will create a new session with the session ID in the "Session ID" text box and timeout value in the "Timeout" text box.
  13. "Delete Session" button: This button will delete the selected session from the list view "Session List". To prevent you from deleting a session by accident, you must also enter the session ID into the "Session ID" text box when deleting the selected session.
  14. "Create Session Item" button: This will create a new session item using the session ID in the "Session ID" text box, item key in the "Item Key" text box, and using an empty string as data value.
  15. "Update Selected Item" button: Click this button to update the selected session item after you have modified the XML content in the "Session Item Selected" text box.

XML representation of .NET object in SessionBrowser.exe

Here is an example of the XML representation of a selected session item:
<?xml version="1.0" encoding="UTF-8"?>
<SessionItemRoot>
    <SessionID>MySessionID</SessionID>
    <ItemKey>MyItemKey</ItemKey>
    <SessionItem>
        <System.String.Array>
            <System.String>Hello, world</System.String>
            <System.String>Test</System.String>
            <System.String>This is a test</System.String>
        </System.String.Array>
    </SessionItem>
</SessionItemRoot>
The above session item is obviously an array of System.String. If we modify the XML text in text box "Session Item Selected" as follows and click the "Update Session Item" button, the selected session item will be changed into an object array, with the first item a string "Yes, the end is now!", the second item a date time value "12/31/2012 23:59:59", and third item a boolean value "true":
<?xml version="1.0" encoding="UTF-8"?>
<SessionItemRoot>
    <SessionID>MySessionID</SessionID>
    <ItemKey>MyItemKey</ItemKey>
    <SessionItem>
        <System.Object.Array>
            <System.String>Yes, the end is now!</System.String>
            <System.DateTime>12/21/2012 23:59:59</System.DateTime>
            <System.Boolean>true</System.Boolean>
        </System.Object.Array>
    </SessionItem>
</SessionItemRoot>
If you want to know how to turn the above .NET object into XML or turn XML text into a .NET object, see code for the BuildXML method and the BuildObject method in SessionBrowser.exe.
Note: Unfortunately, not all serializable types can be handled by SessionBrowser.exe. Only basic types and arrays of basic types, and object arrays (each object in the object array can be any basic type, array of basic types, or object array, etc.) can be viewed and modified by SessionBrowser.exe. As I said, you can save any serializable .NET object into SharedSesssions, the catch is, you may not be able to view or update some of them using SessionBrowser.exe.
As with any other tool, this one can be used badly, causing endless nightmares. Yes, that is definitely possible if your applications are messing up each other's data in the SharedSessions database. In addition, it is harmful when swallowed and may cause birth defects in certain populations, ... well, I will stop here.

No comments :