|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionJust about every ASP.NET application needs to keep track of data for a user's session. ASP.NET provides the The But (there is always a but) this flexibility does not come without a cost. The cost is the ease with which bugs can be introduced into your application. Many of the bugs that can be introduced will not be found by unit testing, and probably not by any form of structured testing. These bugs often only surface when the application has been deployed to the production environment. When they do surface it is often very difficult, if not impossible, to determine how they occured and be able to reproduce the bug. This means they are very expensive to fix. This article presents a strategy to help prevent this type of bug. It uses a Design Pattern called a Facade, in that it wraps the very free interface provided by the The example code shown in this article is written in C#, but the concepts are applicable to any .NET language. What is the problem?In this section of the article I will describe the problems with direct access to the The following shows the typical code written to access session-state variables. // Save a session variable
Session["some string"] = anyOldObject;
// Read a session variable
DateTime startDate = (DateTime)Session["StartDate"];
The problems arise from the flexibile interface provided by Using String Literals as KeysIf string literals are used as the keys, the string value of the key is not checked by the compiler. It is easy to create new session values by simple typing errors. Session["received"] = 27;
In the code above, two separate session values have been saved. Most bugs like this will be identified by unit testing – but not always. It may not always be apparent that the value has not changed as expected. We can avoid this kind of bug by using constants: private const string received = "received";
No Type CheckingThere is no type checking of the values being stored in session variables. The compiler cannot check correctness of what is being stored. Consider the following code: Session["maxValue"] = 27;
Elsewhere the following code is used to update the value. Session["maxValue"] = 56.7;
If the code to read the "maxValue" session variable into the maxValue Most bugs like this will be identified by unit testing – but not always. Re-using a Key UnintentionallyEven when we define constants on each page for the session keys, it is possible to unintentionally use the same key across pages. Consider the following example: Code on one page: private const string edit = "edit";
Code on a second page, displayed after the first page: private const string edit = "edit";
Code on a third, unrelated, page: private const string edit = "edit";
If the third page is displayed for some reason before the second page is displayed, the value may not be what was expected. The code will probably strill run, but the results will be wrong. Usually this bug will NOT be picked up in testing. It is only when a user does some particular combination of page navigation (or opening a new browser window) that the bug manifests. At its worst, no one is aware that the bug has manifested, we may just end up modifying data to an unintended value. Re-using a Key Unintentionally - againIn the example above, the same data type was stored in the session variable. Because there is no type checking of what gets stored, the problem of incompatible data types can also occur. Code on one page: Session["FollowUp"] = "true";
Code on a second page: Session["FollowUp"] = 1;
Code on a third page: Session["FollowUp"] = true;
When the bug manifests there will be an Usually this bug will NOT be picked up in testing. It is only when a user does some particular combination of page navigation (or opening a new browser window) that the bug manifests. What Can We Do?The First Quick FixThe first and most simple thing we can do is make sure we never use string literals for session keys. Always use constants and so avoid simple typing mistakes. private const string limit = "limit";
However, when constants are defined locally (e.g. at page level) we might still re-use the same key unintentionally. A Better Quick FixRather than define constants on each page, group all session key constants into a single location and provide documentation that will appear in Intellisense. The documentation should clearly indicate what the session variable is used for. For example, define a class just for the session keys: public static class SessionKeys
{
/// <summary>
/// The maximum ...
/// </summary>
public const string Limit = "limit";
}
...
Session[SessionKeys.Limit] = 27;
When you need a new session variable, if you choose a name that has already been used you will know this when you add the constant to the However, we are still not ensuring consistency of data type. A Much Better Way - Using a FacadeOnly access the All session variables will be exposed as properties of the facade class. This has the same advantages as using a single class for all the session keys, plus the following advantages:
An Example Session Facade ClassHere is an example class to implement the Session facade for an application called MyApplication. /// <summary>
/// MyApplicationSession provides a facade to the ASP.NET Session object.
/// All access to Session variables must be through this class.
/// </summary>
public static class MyApplicationSession
{
# region Private Constants
//---------------------------------------------------------------------
private const string userAuthorisation = "UserAuthorisation";
private const string teamManagementState = "TeamManagementState";
private const string startDate = "StartDate";
private const string endDate = "EndDate";
//---------------------------------------------------------------------
# endregion
# region Public Properties
//---------------------------------------------------------------------
/// <summary>
/// The Username is the domain name and username of the current user.
/// </summary>
public static string Username
{
get { return HttpContext.Current.User.Identity.Name; }
}
/// <summary>
/// UserAuthorisation contains the authorisation information for
/// the current user.
/// </summary>
public static UserAuthorisation UserAuthorisation
{
get
{
UserAuthorisation userAuth
= (UserAuthorisation)HttpContext.Current.Session[userAuthorisation];
// Check whether the UserAuthorisation has expired
if (
userAuth == null ||
(userAuth.Created.AddMinutes(
MyApplication.Settings.Caching.AuthorisationCache.CacheExpiryMinutes))
< DateTime.Now
)
{
userAuth = UserAuthorisation.GetUserAuthorisation(Username);
UserAuthorisation = userAuth;
}
return userAuth;
}
private set
{
HttpContext.Current.Session[userAuthorisation] = value;
}
}
/// <summary>
/// TeamManagementState is used to store the current state of the
/// TeamManagement.aspx page.
/// </summary>
public static TeamManagementState TeamManagementState
{
get
{
return (TeamManagementState)HttpContext.Current.Session[teamManagementState];
}
set
{
HttpContext.Current.Session[teamManagementState] = value;
}
}
/// <summary>
/// StartDate is the earliest date used to filter records.
/// </summary>
public static DateTime StartDate
{
get
{
if (HttpContext.Current.Session[startDate] == null)
return DateTime.MinValue;
else
return (DateTime)HttpContext.Current.Session[startDate];
}
set
{
HttpContext.Current.Session[startDate] = value;
}
}
/// <summary>
/// EndDate is the latest date used to filter records.
/// </summary>
public static DateTime EndDate
{
get
{
if (HttpContext.Current.Session[endDate] == null)
return DateTime.MaxValue;
else
return (DateTime)HttpContext.Current.Session[endDate];
}
set
{
HttpContext.Current.Session[endDate] = value;
}
}
//---------------------------------------------------------------------
# endregion
}
The class demonstrates the use of property getters that can provide default values if a value has not been explicitly stored. For example, the The property getter for the The The following code shows how a session variable can be accessed through the facade. Note that there is not need to do any casting in this code. // Save a session variable
MyApplicationSession.StartDate = DateTime.Today.AddDays(-1);
// Read a session variable
DateTime startDate = MyApplicationSession.StartDate;
Additional BenefitsAn additional benefit of the facade design pattern is that it hides the internal implemention from the rest of the application. Perhaps in the future you may decide to use another mechanism of implementing session-state, other than the built-in ASP.NET HttpSessionState class. You only need to change the internals of the facade - you do not need to change anything else in the rest of the application. SummaryThe use of a facade for | ||||||||||||||||||||