Introduction
Web Application State
A Web page is recreated every time it is posted back to the server. In traditional Web programming, this means that all the information within the page and information in the controls is lost with each round trip. To overcome this limitation of traditional Web programming, the ASP.NET page framework includes various options to help us preserve changes. One option involves keeping information on the client, directly in the page or in a cookie, and another option involves storing information on the server between round trips. The following objects are designed to hold application state: HttpSessionState
, HttpApplicationState
, ViewState
, HttpCookie
, HiddenFiled
, and also querystrings and databases can act as state holders. So, we can say that there are mainly two different ways to manage a web application’s state: client-side and server-side.
Client-side State Management
Cookies
A cookie is a small amount of data stored either in a text file on the client's file system or in-memory in the client browser session. Cookies are mainly used for tracking data settings. Let’s take an example: say we want to customize a welcome web page; when the user requests the default web page, the application firsts detects if the user has logged in before; we can retrieve the user information from cookies, in this case.
Hidden Fields
The default way of saving the state of data is to use HTML hidden fields. A hidden field stores text data with the HTML <input type="hidden">
. A hidden field will not be visible in the browser, but we can set its properties just like we set properties for other standard controls. When a page is posted to the server, the content of a hidden field is sent in the HTTP Form
collection along with the values of other controls. In order for hidden field values to be available during page processing, we must submit the page.
ViewState
The Control.ViewState
property provides a way for retaining values between multiple requests for the same page. This is the method that the page uses to preserve page and control property values between round trips. When a page is processed, the current state of the page and the controls is hashed into a string and saved in the page as a hidden field. When the page is posted back to the server, the page parses the view state string at page initialization and restores property information in the page.
Query Strings
Query strings provide a simple but limited way of maintaining some state information. You can easily pass information from one page to another, but most browsers and client devices impose a 255-character limit on the length of the URL. In addition, the query values are exposed to the Internet via the URL, so in some cases, security may be an issue. A URL with query strings may look like this: http://myapplication.com/listing.aspx?group=1&item=1.
Server-side State Management
Application Object
The Application
object provides a mechanism for storing data that is accessible to all code running within the Web application. The ideal data to insert into application state variables is data that is shared by multiple sessions and which does not change often. And just because it is visible to the entire application, you need to use the Lock
and UnLock
methods to avoid having conflict values.
Session Object
The Session
object can be used for storing session-specific information that needs to be maintained between server round trips and between requests for pages. The Session
object is on a per-client basis, which means different clients generate different Session
objects. The ideal data to store in session-state variables is short-lived, sensitive data that is specific to an individual session.
Using Database
Maintaining state using database is a very common method when storing user-specific information where the information store is large. Database storage is particularly useful for maintaining long-term state or state that must be preserved even if the server must be restarted. The database approach is often used along with cookies. For example, when a user access an application, he might need to enter a username/password to log in. You can look up the user in your database and then pass a cookie to the user. The cookie might contain only the ID of the user in your database. You can then use the cookie in the requests that follow to find the user information in the database, as needed.
Problem
As was stated above, web applications are stateless, so you need to implement data holders to provide state for web applications. For example, the following code demonstrates the basic usage of the HttpSessionState
.
MyDtoObject dtoObject = new MyDtoObject();
dtoObject.FirstName = Session["FirstName"] as string;
dtoObject.LastName = Session["LastName"] as string;
dtoObject.Login = Session["Login"] as string;
But, what if requirements force me to store MyDtoObject
in the database for a while? Then, it forces me store MyDtoObject
in HttpApplicationState
to share it between user sessions. It makes it very difficult if I have to modify batches of files all the time. What if I need ability to define how to store MyDtoObject
, without re-coding?
Solution
I believe that using HttpSessionState
, HttpCookie
, and HttpApplicationState
directly is not convenient, so I designed some kind of wrappers for these objects following the OOP paradigm. For instance, I need to have the ApplicationSettings
object that would hold the state in cookies, and also, it is supposed to be a singleton object. All that I need is just to make the ApplicationSettings
class derive from SingletonStateObject<ApplicationSettings, CookieStorage<ApplicationSettings>>
.
public class ApplicationSettings : SingletonStateObject<ApplicationSettings,
CookieStorage<ApplicationSettings>>
{
private string _hostName;
private int _counter;
public int Counter
{
get { return _counter; }
set { _counter = value; }
}
public string HostName
{
get { return _hostName; }
set { _hostName = value; }
}
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
ApplicationSettings settings = ApplicationSettings.Get();
Response.Write("ApplicationSettings:");
Response.Write("<br/>");
Response.Write("HostName: " + settings.HostName);
Response.Write("<br/>");
Response.Write("Counter: " + settings.Counter);
}
protected void uxSave_Click(object sender, EventArgs e)
{
ApplicationSettings settings = ApplicationSettings.Get();
settings.HostName = uxHostName.Text;
settings.Counter++;
settings.Save();
Response.Redirect("~/SingletonDefault.aspx");
}
The SingletonStateObject
and StateObject
are base classes for any state object. Both these classes deal with IStorage<T>
implementators.
The IStorage
interface defines a contract for saving, retrieving, and removing data from storage.
IStorage Implementators
The StateObject
, SingletonStateObject
, and IStorage
implementators together demonstrate the Bridge pattern which allows to define your own state object in a flexible manner. Depending on your needs, you can use a concrete storage implementator or implement your own.
- Use
CookieStorage
if you need to store small amounts of information on the client, and security is not an issue.
- Use
ApplicationStorage
if you need to store small amounts of information that is infrequently changed, and information is shared between users.
- Use
SessionStorage
if you need to store short-lived information that is specific to an individual session, and security is an issue. Do not store large quantities of information in a session state object. Be aware that a session state object will be created and maintained for the lifetime of every session in your application. In applications hosting many users, this can occupy significant server resources and affect scalability.
- Use
DoaStorage
if you need to store large amounts of information managing transactions, or the information must survive application and session restarts, and security is an issue.
DaoStorage Implementation
Let's get back to the ApplicationSettings
object. What if requirements force me to change storage and ApplicationSettings
is not singleton any more? The requirements force storage to survive Application
and Session
restarts, so we need to use a database to hold state. There is a Heap table that has Key
and Value
columns in the MS Access database attached to the sample project. All that I need to do is to serialize the object and save it using the provided key.
public class DaoStorage<T> : IStorage<T> where T : class
{
public void Save(object key, T obj)
{
string value = Serializer<T>.Serialize(obj);
using (OleDbConnection connection =
new OleDbConnection(Config.ConnectionString))
{
connection.Open();
if (IsExist(connection, key))
Update(connection, key, value);
else
Insert(connection, key, value);
}
}
public T Get(object key)
{
using (OleDbConnection connection =
new OleDbConnection(Config.ConnectionString))
{
connection.Open();
string obj = Get(connection, key);
return Serializer<T>.Deserialize(obj);
}
}
public void Delete(object key)
{
using (OleDbConnection connection =
new OleDbConnection(Config.ConnectionString))
{
connection.Open();
Delete(connection, key);
}
}
public List<T> GetAll(Type type)
{
using (OleDbConnection connection =
new OleDbConnection(Config.ConnectionString))
{
connection.Open();
return GetAll(connection);
}
}
public void Clear()
{
using (OleDbConnection connection =
new OleDbConnection(Config.ConnectionString))
{
connection.Open();
Clear(connection);
}
}
....
In this class, I use XmlSerialazer
to put objects into database, so we need to keep in mind that ApplicationSettigs
must be XmlSerializable
. Let's modify the ApplicationSettings
class:
public class ApplicationSettings : StateObject<ApplicationSettings,
DaoStorage<ApplicationSettings>>
{
private string _hostName;
private int _counter;
public int Counter
{
get { return _counter; }
set { _counter = value; }
}
public string HostName
{
get { return _hostName; }
set { _hostName = value; }
}
}
That is all, now the ApplicationSettings
class is not singleton, and also doesn't depend on Application
and Session
restarts any more. Instead of using HttpSessionState
or HttpApplicationState
, it serializes its state into the database.
Scalability
Using HttpSessionState
has performance issues and using cookies has security issues. There are many alternative storages to use, but what if I need to have a configurable storage strategy for a group of objects? Let's design the ConfigurableStorage
which allows us to define a storage strategy from configuration.
public class ConfigurableStorage<T> : IStorage<T> where T : class
{
private readonly IStorage<T> _storage = ResolveStorage();
private static IStorage<T> ResolveStorage()
{
switch (Config.StorageType)
{
case StorageType.AppicationStorage:
return new ApplicationStorage<T>();
case StorageType.SessionStorage:
return new SessionStorage<T>();
case StorageType.FileStorage:
return new FileStorage<T>();
case StorageType.DaoStorage:
return new DaoStorage<T>();
case StorageType.CookieStorage:
return new CookieStorage<T>();
default:
throw new ApplicationException("StorageType");
}
}
public void Save(object key, T obj)
{
_storage.Save(key, obj);
}
public T Get(object key)
{
return _storage.Get(key);
}
....
As you see, the ConfigurableStorage
is a simple class that creates another type of storage and uses it to save object states. It also demonstrates the Proxy pattern. Let's modify the ApplicationSettings
class:
public class ApplicationSettings : StateObject<ApplicationSettings,
ConfigurableStorage<ApplicationSettings>>
{
private string _hostName;
private int _counter;
public int Counter
{
get { return _counter; }
set { _counter = value; }
}
public string HostName
{
get { return _hostName; }
set { _hostName = value; }
}
}
Now, the ApplicationSettings
derives from ConfigurableStorage
as well as other classes that are supposed to be configurable. It is the most painless approach to change the storage strategy for a group of objects in an application.