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)
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:- All variables are Global to the entire application
- All variables are of type Object
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:
- a way to restrict the scope of our variables so only the classes that own them can change them
- access to them through a defined interface so only the values and types that we permit can be stored
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.
Comments