Managing session states in ASP.NET

This article was originally published in VSJ, which is now part of Developer Fusion.
One of the big problems with web application development that you just don’t get when developing for Windows is managing session state. In a Windows application if you set a variable in one method, the next time the user clicks your form, your variable still has its value. Web applications are like goldfish. You set a variable in one method, the next time the user clicks your form, your variable is “forgotten” and has returned to its default value.

This is because ‘web application’ is really a contradiction in terms. Web servers were designed to receive a request for a web page, reply with some static text and images, then wait for the next request. The idea that subsequent requests from the same user could constitute an application is really just a recent afterthought.

The web server and browser combination have evolved over the years to provide a number of tricks to allow a web site to remember what the user did on one page and use that to modify the display on another. Essentially this all entails the server sending a “cookie” to the browser and the browser returning the same “cookie” back to the server on the next request.

The ASP.NET Session

Fortunately, and for those familiar with the .NET framework this will be no surprise, ASP.NET takes care of all the low-level mechanics for us. As ASP.NET developers we are provided with a Session object. The Session object is a collection of Key/Value pairs, and all we need to know is that when we access the Session object the values in there will be unique to the requesting user and anything we put in there will remain until:
  • we delete it
  • we change it
  • the Session expires (through explicit logout or user idle timeout)
The Session object allows any variable to be stored as an object. To store a value in the Session is as simple as indexing using the key name and writing the new value. If a value with that key name does not exist a new one will be created:
protected void Page_Load(object
	sender, EventArgs e)
{
	Session[“myVariable”] = 21;
}
Everything in the Session is stored as an object so must be cast back to the original type before it can be used:
protected void Button1_Click(object
	sender, EventArgs e)
{
	int myVariable =
		(int)Session[“myVariable”];
}
If we have not previously stored a value for a given key before it is accessed, a NullReferenceException will be thrown. This is fine for simple applications that only comprise a few pages. However, as the scope of the application increases, a random bunch of objects becomes increasingly unmanageable and unmaintainable.

Property access

There are two fundamental problems with the Session object:
  1. All variables are Global to the entire application
  2. All variables are of type Object
As Session Key/Value can be set from anywhere in the application code it is impossible to know at any given point if a value will definitely have been set, if it has, it could have been overridden with a new value elsewhere in the code. Worse still, it could have been overridden with a completely different object type. Consider the following, completely legal but totally disastrous piece of code:
protected void Page_Load(object
	sender, EventArgs e)
{
	Session[“myVariable”] = 21;
	Session[“myVariable”] =
		new DropDownList();
}
In an object-oriented language like C# (or VB.NET), having to rely on global untyped objects to manage our application just does not feel right. What we need is:
  1. a way to restrict the scope of our variables so only the classes that own them can change them
  2. access to them through a defined interface so only the values and types that we permit can be stored
A simple solution to this problem is to wrap each variable in a page property. This has the advantage that the scope can be restricted to a given page and consistent range checking and default values are provided via the get and set methods.
protected int myProperty
{
	get
	{
	// Initialize if not already
	// created
		if (Session[“myVariable”]
			== null)
		{
			Session[“myVariable”] = 0;
		}
		return (int)Session[
			“myVariable”];
	}
	set
	{
	// Check value is between 0 and 100
		if ((value > 0) &&
			(value < 100))
		{
			Session[“myVariable”] =
				value;
		}
	}
}
The scope of each property can be controlled beyond a single page by placing them in a base class and deriving application pages from them. This only works if programmers working on each page can be disciplined enough to only access the session variable through the Page properties. For example, on a page that does not have access to myProperty the developer can still write:
Session[“myVariable”] =
	new DropDownList();
The error will not be apparent until the value in the “myVariable” key is next cast to an int!

The Typed Session

But this isn’t the end of the story. In a large application you can end up with hundreds of variables to maintain the application logic. Session variables wrapped in page properties are a good place for keeping page level state information, e.g. the active tab or selected row, but they are not a good place to keep all your business logic.

In the Typed Session pattern we keep all of our business logic and application variables in an object and store this one object in the Session. For example:

public class AppModel
{
	private int myVariable;
	public const string SessionKey =
		“AppSession”;
	public AppModel()
	{
		myVariable = 0;
	}
	public int MyVariable
	{
		get { return _myVariable; }
	}
	public void DoAppLogic()
	{
		myVariable = someFunction();
	}
}
The entire business logic object can then be wrapped in a single page property:
protected AppModel Model
{
	get
	{
		if (Session[
		AppModel.SessionKey] == null)
		{
			Session[AppModel.SessionKey]
				= new AppModel();
		}
	return (AppModel)Session[
		AppModel.SessionKey];
	}
}

Singleton Typed Session

This still doesn’t prevent the application from instantiating new AppModel objects and storing them in the Session. To avoid this we can use the Singleton design pattern for our AppModel object. The Singleton design pattern ensures that only one instance of a class can ever be instantiated within an application. Much has been written on the implementation of the Singleton pattern, but in essence it relies on making the constructor of a class private and exposing the only instance available to the application via a public static member variable. The difference in our example is that the single instance is not stored in a private member variable but in the .NET Session collection:
using System.Web;
public class AppModel
{
	private int myVariable;
	private const string SessionKey =
		“AppSession”;
	private AppModel()
	{
		myVariable = 0;
	}
/// <summary>
/// Singleton reference to App Model
/// </summary>
	public static AppModel Instance
	{
		get
		{
			if (HttpContext.Current.
			Session[SessionKey] == null)
			{
				AppModel model =
					new AppModel();
				HttpContext.Current.
			Session[SessionKey] = model;
			}
		return (AppModel
			)HttpContext.Current.
			Session[SessionKey];
		}
	}
	public void DoAppLogic()
	{
		myVariable = someFunction();
	}
}
To get to the user’s Session in a class that is not derived from System.Web.UI.Page we have to use the HttpContext.Current object in the System.Web namespace. The HttpContext.Current will return details of the current page request, including the Session object.

Our application now has structured access to a business logic module that is persisted across page requests for the lifetime of the user’s session and the scope of any variable or function can be controlled using standard scope modifiers (private, protected, public etc).

protected void Button1_Click(
	object sender, EventArgs e)
{
	AppModel.Instance.DoAppLogic();
}

Destroy on Logout

Most web applications these days require some form of authentication or Login. Consequently there is a Logout stage when the user is finished with the application. To prevent the session variable being available after the user has logged out it must be destroyed. To destroy all variables stored in connection with the user’s session the ASP.NET Session object provides a handy “Abandon()” method. We can encapsulate this in our AppModel class by again using HttpContext.Current to get at the current users session:
public class AppModel
{
	public void DoLogout()
	{
	// Close any open database connections.
	// Free any open file handlers etc.
		... snip
		HttpContext.Current.Session.Abandon();
	}
	... snip
}
A convenient place to call this is to create a new page “Logout.aspx”. In the Page Load event of the logout class call the DoLogout() method.
public partial class Logout :
	System.Web.UI.Page
{
	protected void Page_Load(
		object sender, EventArgs e)
	{
		AppModel.Instance.DoLogout();
		Response.Redirect(
			Page.ResolveUrl(
			“~/” + “Login.aspx”));
	}
}
To securely log out of the application you just need to provide a link to Logout.aspx.

Real world use

Our example is still quite contrived and storing all business logic in one class called AppModel would quickly reach its limitations in a real application. As application complexity increases business logic in the AppModel class can be subdivided along traditional lines (e.g. a data tier and dedicated business logic modules). For example:
public class AppModel
{
	private CDataTier m_data;
	private CCustomerModule m_customer;
	private COrderProcessingModule
		m_orders;
	private AppModel()
	{
		m_data = new CDataTier(sDSN);
	}
... snip
}
The advantage of using the Typed Session pattern is that the contained business logic classes can be built in Class Libraries and reused in other Windows, Web, Service etc applications without modification as the messy business of storing them in the user session is taken care of by the AppModel class.


Robert Faulkner is a freelance software developer with over 12 years’ programming experience, currently specialising in .NET for web, Windows and Compact Framework applications.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Every language has an optimization operator. In C++ that operator is ‘//’”