Apress
ASPToday
       1070 Articles
  in the Solutions Library
  Log off
 
 
 
BrowserHawk green ad  
 
ASPToday Subscriber's Article Rahul Pitre
A Custom Class for ASP.NET Session Variables
by Rahul Pitre
Categories: Security/Admin, .NET Framework
Article Rating: 4 (1 rater)
Published on August 27, 2003
 
Content Related Links Discussion Comments Index Entries Downloads
 
Abstract
The Session object in ASP.NET offers a convenient solution to the problem of maintaining state during the lifetime of a session. But it is not a very convenient object to deal with during development. The VisualStudio.NET editor does not provide intellisense for session variables, for instance. This article shows you how to build a class that will enable you to organize and manipulate your session variables better without incurring significant overhead.
 
Article Information
Author Rahul Pitre
Editor Simon Robinson
Reviewers Douglyss Giuliana, David Schultz
Copy Edits David Schultz
 
Article

Introduction

ASP.NET provides the intrinsic Session object as a convenient storage facility to persist small data items. (Note the emphasis on small . If you store recordsets or monstrous arrays in session state, you shouldn't be discussing the subtleties of ASP.NET session state; you should be fixing the architecture of your application!) Session variables save you a lot of custom state maintenance code that you had to write before the advent of ASP. But they are not easy to deal with at design time. For instance, you must remember or look up session variable names while coding because VisualStudio.NET cannot provide intellisense for them. You must remember their data types too. An incorrect cast is not caught by the compiler and manifests only at run time.

You can work around these and many similar annoyances by creating a custom class for your session variables. The class will make your life easier when using the VisualStudio.NET editor. You will be able to build code faster, organize your code better and your code will have fewer bugs.

System Requirements

To follow along with this article, you will need the following software:

  • Windows 2000 Professional/Windows 2000 Server/ Windows XP/Windows 2003 Server
  • The .NET framework (including ASP.NET) version 1.0 or later
  • Visual Studio .NET

You also need the following background:

  • Familiarity with ASP.NET using VB.NET
  • Firsthand experience with session variables in order to appreciate their limitations
  • Familiarity with Object Oriented Programming

The accompanying sample code is written in VB.NET using version 1.0 of the framework and tested on a Windows 2000 Server.

The Problem: Session Variables Are Difficult To Code With

In the introduction, I briefly mentioned that I find session variables awkward to deal with while coding. Here is a more detailed list of reasons why:

  • Normally, you must declare a variable before you can manipulate it. (This is always the case in C# and is the case in VB.NET if Option Strict or Option Explicit is on.) Not so with session variables. You can manipulate Session("anything") without declaring it first. It is not flagged with the familiar squiggly line or a compilation error. This results in programmers adding variables to the session all over the application. I would like to have a way to force programmers to declare all session variables in one place for easy reference.
  • Once you decide on a name, you must type it in VisualStudio.NET editor.VisualStudio.NET cannot provide intellisense for session variables because the environment cannot obtain type information that intellisense requires. This compels your to remember a variable's name. A typographical error can be disastrous. For instance, if you type Session("ProductOd")= 3 when you intended to type Session("ProductId") = 3, you create two problems. First, you unknowingly create a superfluous variable. Second, when you check the value of Session("ProductId") on another page, it does not contain the value you expect it to. Tracking down bugs introduced by inadvertently using the same session variable name for two distinct entities can be very tricky. I would like to have a way to get intellisense for session variables in VisualStudio.NET environment
  • Type safety is a core feature of the Common Language Runtime. The managed compliers are designed to throw a fit at the slightest hint of type safety violation. No matter what type of value you store in it, a session variable is always of the type Object. You must not only remember the data type of the value stored in a session variable, you must also explicitly cast it to that data type every time you use the variable. An incorrect cast manifests at the least desirable time - run time. I would like to have a way to catch casting violations at compile time rather than at run time.
  • Properties of an object can be used to store data as well as to execute code. Setting a session variable is often logically associated with setting other session variables or executing some piece of code. For example, every time you get a product id, you may want to fetch its name from the database and set a corresponding session variable as well. I would like the ability to encapsulate associated logic with session variables.
  • Values stored in Session are always modifiable; you can't declare ReadOnly session variables. Consider the example in the previous bullet point. The name of the product should probably not be modifiable on its own since it changes in conjunction with the product id. I would like the ability to declare ReadOnly session variables.

The Solution: Create A Custom Class

To arrive at a solution, you must first understand what causes the problem. When you store values as session variables, they get added to the intrinsic Session object, which is an instance of HttpSessionState class. This type is implemented as a weakly typed collection, which stores entities of type Object. This gives you some flexibility and you can store pretty much anything in the Session object. But the flexibility comes at a cost. When you store an instance of any type in a collection, it gets cast to the type Object. You must cast it back to its appropriate type upon retrieval. The compiler has no knowledge of the intended type of the object on retrieval and, it is unable to catch casting errors. VisualStudio.NET faces the same problem. It can't determine the type of an object when you refer to it in the editor as a member of a collection and consequently, can't provide intellisense.

Armed with this knowledge, you can devise a solution as follows:

  • Implement all your session variables as properties of a custom class. Properties can have types and can execute code.
  • Instead of adding many individual variables to the intrinsic Session object, add a single instance of the custom class to it when a session begins. You will now have a solitary session variable.
  • In the Load event of each page, reference the instance of your class from Session and cast it appropriately. While coding you will refer to your custom class rather than the session object. VisualStudio.NET can access type information for your class and provide intellisense. The compiler can use the type information to perform casting checks.
  • Retrieving and casting the object on every page can quickly become cumbersome. So abstract the functionality into a class inherited from Page class and use the descendent class as the ancestor of all our pages.

Let's write a small application based on the outline above to demonstrate the technique. Here is an overview of the steps:

  1. Create a new project
  2. Create a class to hold the session variables you want to define. I will call the class SessionManager.
  3. Declare a property in the class that represents each session variable you want to store. You can make your properties ReadOnly if you wish and also assign them types.
  4. Declare a constructor for the class so as to "capture" built-in Session properties such as SessionID.
  5. In the Session_Start event in Global.asax, declare and instantiate an object of the type SessionManager and add the object to the intrinsic Session object with a variable name, say, ThisSession.
  6. Inherit a class from System.Web.UI.Page class and use it as the ancestor class for all the pages in your project. I will call this class PageManager.
  7. Declare a Protected object of the type SessionManager called CurrentSession, in PageManager class. Fetch the ThisSession object from the session and assign it to the CurrentSession variable in Page_Load event.
  8. Add the necessary UI to the application to test the class.

The Code

Create a New Project

Create a new ASP.NET Web Application project under Visual Basic Projects and call it SessionManagerDemo. Make sure from the Project/Add Reference menu item that System.Web.dll has been referenced

Declare SessionManager Class

Add a new class file to the project and call it SessionManager.vb. Create a class as follows:

<Serializable()> Public Class SessionManager

End Class

Notice the <Serializable> attribute in the class declaration. It indicates to the runtime that this object can be serialized. If you use StateServer or SqlServer session state modes, your application needs the ability to serialize the data stored in the session object out to the state server process or the database and deserialize it when it is retrieved. The <Serializable> attribute enables the CLR to use the framework's binary formatter to serialize and deserialize session state data if and when required. You don't need to worry about how it is done; ASP.NET automatically takes care of it for you. If your application uses the InProc mode, you don't need to specify the attribute. But there is no harm in including it.

Incidentally, if you are using StateServer or SqlServer session state modes and you fail to include the <Serializable> attribute, the application will throw an exception.

Declare session variables as properties or methods of SessionManager

Declare two variables, one to hold a product id and the other for product name. Declare and initialize the variables as private variables in SessionManager class as follows:

    Private _productId As Int32 = 0
    Private _productName As String = "Undefined"

Next, declare corresponding properties and methods to get and set these variables. To illustrate ReadOnly variables and the ability to execute code from properties, I have defined the ProductName property to be ReadOnly. The Set accessor of ProductId property, will call the private method SetProductName , which sets the value of the private variable _productName. Notice that each private variable and its corresponding property have a specific type.

    Public Property ProductId() As Int32
        Get
            Return _productid
        End Get
        Set(ByVal Value As Int32)
            _productid = Value
            SetProductName(Value)
        End Set
    End Property

    Public ReadOnly Property ProductName() As String
        Get
            Return _productname
        End Get

    End Property

    Private Sub SetProductName(ByVal productid As Int32)
        'Some code here that will fetch the name 
        'from the database. Pretend the name is "Thingamajig"
        _productName = "Thingamajig"
    End Sub


While you code your application, you will come back here from time to time to add properties and methods for every new session variable you want to declare.

Declare a new constructor for the class

You often have to use Session.SessionID in your code. For instance, your application may have to collect user input over a series of pages and persist it in database tables until it is processed during the course of the session. In such a situation, you need a unique identifier that will enable you to associate the input with a specific session being run by a specific user. You may decide that the combination of session id and user id suits the purpose and use it as the unique identifier. The session id does not change during the session. Therefore, you can create it to be a read only property on SessionManager and set it in the object's constructor. You can propagate any built-in property of the intrinsic session object to SessionManager in this manner.

    Private _sessionId As String = "Undefined"


    Sub New(ByVal sessionid As String)
        _sessionId = sessionid
    End Sub

I have chosen to capture the session id and store it in the custom class because when I want to refer to the session id in the code, I would like to refer to it as a property of the custom class rather than as a property of the session object. You can set other intrinsic properties that you want to refer to in your code in this fashion as well and obviate the need to refer to the Session object directly in your Web Forms. Each of the properties you propagate to the custom class will require an accessor. Declare one for the session id as follows:

    
    Public ReadOnly Property SessionId() As String
        Get
            Return _sessionId
        End Get
    End Property

Code Session_Start Event in Global.asax

In Global.asax, add the following code to Session_Start Event

   Dim ThisSession As SessionManager = New SessionManager(Session.SessionID)
   Session.Add("CurrentSession", ThisSession)

This creates a new instance of the SessionManager class and adds it to the intrinsic session.

Declare PageManager Class

Add a new class file to the project and call it PageManager.vb. Create a class as follows:

   
    Public Class PageManager
    	Inherits System.Web.UI.Page 

    End Class

The PageManager class will replace System.Web.UI.Page as the ancestor for all Web Forms in the project. The PageManager class is derived from System.Web.UI.Page class because the application still needs all its functionality.

Creating PageManager class is not strictly necessary. You can achieve the same effect by instantiating CurrentSession on every page instead. It is a good idea, however, to create an ancestor class for your pages because it provides an additional layer of abstraction that allows you to write code that you want all your pages to inherit.

Notice that I created SessionManager and PageManager classes in two separate physical files. This is not necessary either, but it is a good idea. Keeping each class in its own file allows you to locate a class quickly and check classes in and out individually in a source control manager such as SourceSafe.

Add code to PageManager class

You now have an instance of SessionManager in the intrinsic Session. All you have to do is create a reference to it during the loading of the page. Once you do that, the functionality of SessionManager class will be available to all pages derived from PageManager.

Add the following code to the PageManager class to do just that.

    
    Protected CurrentSession As SessionManager

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As _   
 System.EventArgs) Handles MyBase.Load
        'Retrieve Sessionmanager from  the Session and assign 
 'to a local variable
        CurrentSession = DirectCast(Session("CurrentSession"), 
SessionManager)
    End Sub

As we saw earlier, when you want to manipulate an object stored in the intrinsic session, you must first cast it to its correct type. The code above retrieves the object stored as Session("CurrentSession") and casts it to it's correct type, namely SessionManager. VisualBasic.NET provides two operators for casting: DirectCast and the more commonly used CType. DirectCast is the better option of the two because it is more efficient. However, it can only be used for casting reference types. Since we are dealing with reference types here, I chose to use DirectCast instead of CType

Testing The Code

Its time to verify that the class functions as advertised. To do so, we need to write a couple of WebForms. Follow the steps below

  • Add a new Web Form to the project. Call it FirstForm.aspx
  • Open the code behind file FirstForm.aspx.vb .

Change the line

Inherits System.Web.UI.Page 

to

   Inherits PageManager
  • In Page_Load event, write the following Code
CurrentSession.ProductId = 3

The moment you type the dot after CurrentSession, an intellisense pop-up appears. (See figure 1). While you are at it, try assigning a String value such as "abcd" to CurrentSession.ProductId. The customary squiggly line will appear right away (See figure 2). Try misspelling "ProductId" and you will again see the squiggly line underscoring the error (See figure 3). So you see it is difficult to make mistakes with session variables when you use the class.

Figure 1: Intellisense for session variables

Figure 2: Incorrect type assignments underscored by squiggly lines

Figure 3: Typos are caught at design time and underscored by squiggly lines

  • In Button1_Click event, write the following code
Response.Redirect ("SecondForm.aspx")

  • Save and close the file.
  • Add another Web Form to the project and call it SecondForm.aspx.
  • Open the code behind file SecondForm.aspx.vb and change the second line as you did with FirstForm to
Inherits PageManager

  • In Page_Load event, add the following code
Response.Write("Session Id of the Intrinsic Session object is :" & 
Session.SessionID)
        Response.Write("<BR>")
        Response.Write("SessionId propagetd by SessionManager class is :" _
 & CurrentSession.SessionId)
        Response.Write("<BR>")
        Response.Write("ProductId propagated by SessionManager class is :" _
 & CurrentSession.ProductId)
        Response.Write("<BR>")
        Response.Write("ProductName propagated by SessionManager class is :" _
& CurrentSession.ProductName)

  • Save and close the file.
  • Set FirstForm.aspx as Start Page in Solutions Explorer.

Run the application. When FirstForm comes up, click the button. You will see the session variable propagated to SecondForm (See figure 4).

Figure 4: Session variables are propagated to SecondForm.aspx

Evaluating Impact on Performance

Adding a layer of abstraction negatively affects performance. We must evaluate the impact and judge whether the tradeoff is worth the effort.

Outwardly, this whole mechanism seems to be dubious from the performance point of view because it appears that you end up instantiating the local variable CurrentSession every time a page is invoked. Shouldn't this use up memory on the managed heap and send the garbage collector out working furiously?

Not really. The class has very little overhead. SessionManager is a reference type. The runtime allocates memory for the value stored in an instance of a reference type on the managed heap and allocates a reference to the value on the stack.

When you first instantiate a variable of the type SessionManager in the Session_Start event, the statement

Dim ThisSession As SessionManager = New SessionManager(Session.SessionID)

causes the runtime to allocate memory for an object of SessionManager type on the managed heap and create a reference to it on the stack. Next, the statement

Session.Add("CurrentSession", ThisSession)

Will cause the runtime to create a reference to the memory allocated for ThisSession on the heap and store it in a variable (effectively) called Session("CurrentSession")on the stack. The statement

Protected CurrentSession As SessionManager = 
DirectCast(Session("CurrentSession"), SessionManager)

in Page_Load event of PageManager class does not cause the runtime to physically allocate another chunk of memory on the managed heap because a constructor of SessionManager class is not invoked in the code. The runtime just creates another reference to the same block of memory that is allocated to ThisSession variable stored in the intrinsic Session object and stores a reference to it in a local variable called CurrentSession on the stack. When the page goes out of scope, the runtime unwinds the stack and frees up the memory taken up by the reference (CurrentSession). Reference pointers are only 32 bits long and hence this mechanism is not nearly as memory intensive as it appears at first glance.

You may also have noticed that you retrieve the session information into the CurrentSession variable during the Page_Load event of PageManager but never seem to load CurrentSession back into the intrinsic Session object with a statement such as

Session("CurrentSession") = CurrentSession

The reason again, is that every time you modify a data item in the local CurrentSession variable, the memory location it refers to on the managed heap is modified. The ThisSession variable stored in the intrinsic Session object refers to the same physical location on the heap and it too, in effect, is "modified". This is somewhat like passing a parameter by reference.

We can confirm these assertions programmatically. The Is operator in VisualBasic.NET checks whether two variables are identical, i.e. if two variables refer to the same physical memory location (as opposed to "=" operator which checks whether two variables contain the same value). Add a button and a label to SecondForm.aspx. If you have followed along with the code, they will be called Button1 and Label1 respectively. In Button1_Click event, add the following code

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As _
System.EventArgs) Handles Button1.Click
    Label1.Text = ((CurrentSession Is Session("CurrentSession")).ToString()  & _
     " : CurrentSession and Session(&quot;CurrentSession&quot;) are Identical!")
End Sub

Run the application and click your way to SecondForm.aspx. Click the button and you will see the label populated with "True". (See figure 5). By the way, the class Object has a shared method called ReferenceEquals that compares two variables to determine whether they both point to the same object. You can use that method as follows instead of the Is operator to arrive at the same boolean value.

Label1.Text = Object.ReferenceEquals(CurrentSession, _
    Session("CurrentSession")).ToString())

Figure 5: CurrentSession is identical to Session("CurrentSession")

The Sample Application

To run the sample application in the downloadable code

  • Unzip the downloaded zip file and extract the SessionManagerDemo folder.
  • Copy the folder to your Inetpub/wwwroot folder.
  • Bring up Internet Services Manager and right click on SessionManagerDemo folder to bring up the properties window. Click the Create button and then click OK. Close Internet Services Manager.
  • Open a browser window and enter the URL http://localhost/SessionObjectDemo/FirstForm.aspx?WROXEMPTOKEN=WROXEMPTOKENVALUE and hit enter.
  • You should see the session variables displayed on SecondForm in the browser.

The sample application contains descriptive text on FirstForm and SecondForm, which I have omitted in the article for brevity.

You can also run the application online at http://www.designerwebsites.com/pitre/samples/SessionmanagerDemo/FirstForm.aspx?WROXEMPTOKEN=WROXEMPTOKENVALUE

Conclusion

You have just seen how to work around the lack of design-time support for the intrinsic Session object by creating a custom class to hold your session variables. The class encapsulates all session variables and related code. It compels you to declare and initialize all your session variables in one place. It streamlines your code and helps reduce bugs by providing intellisense and type checking at design time. Also it does not add a noticeable overhead in doing so.

The custom class, however, is not the panacea. It does not prevent you from indiscriminately declaring Session variables outside of the class. Nor does it stop you from storing huge arrays or recordsets in the Session. It is merely a tool that helps you use session variables more effectively.

 
 
 
Rate this Article
How useful was this article?
Not useful Very useful
Brief Reader Comments: Read Comments
Your name (optional):
 
 
Content Related Links Discussion Comments Index Entries Downloads
 
Back to top