ASP.NET applications typically have to deal with session variables. It's not uncommon to have to deal with lots of them, and if you're not careful, you can have the variable names sprinkled liberally throughout your code. When it comes time to refactor those session variables--such as when one needs to be renamed or becomes obsolete--this becomes a maintenance headache. Further, you may find yourself constantly trying to remember what the string key is that references a particular session variable.
In large, multi-developer projects, this can be especially problematic. If there's no centralized location for managing session variables, they may not be documented very well, if at all. How is a particular session variable used? What is its data type? When is it meaningful? When was it introduced? What pieces of the software use it? Is it still used?
Without some discipline, session variables can quickly become unmanageable. In applications with large numbers of users, where the amount of memory consumed by your sessions is important, this can be a serious consideration. You need to be able to quickly deprecate obsolete variables and identify every point in the code that uses them. You need to be able to quickly change the name of a variable without impacting the entire system or using kludgy search-and-replace features.
Enter encapsulation and information hiding, key principles of object oriented programming. In this article, we'll talk about a class that encapsulates the details of working with session variables. It will completely abstract away the details of the key names, so you don't have to remember them, or sprinkle those string constants throughout your code. Further, it will provide strongly-typed properties that do away with the need to perform type casting at the point of call. It will allow you to determine if you even have a valid session object to begin with, before you attempt to query it for information. And, it will allow you to quickly deprecate variables in the event that one becomes obsolete. Finally, it will make it easy to refactor the string key should you need to do so.
Using the Code
The class we're talking about is called
MySession. Because it's so small and straightforward, I've included its full code right here.
Option Explicit On
Option Strict On
Friend NotInheritable Class MySession
Private Const UserIdKey As String = "UserId"
Private Sub New()
Private Shared ReadOnly Property Session As HttpSessionState
Public Shared ReadOnly Property Exists() As Boolean
Return Not Session Is Nothing
Public Shared Property UserId As Integer
Return DirectCast(Session(UserIdKey), Integer)
Set(ByVal value As Integer)
Session(UserIdKey) = value
To use the class, you simply need to drop it into your application, remove the sample property, and add one property for every session variable you use. Make sure that each property you define is marked
Shared. Then, in your code, instead of directly accessing session variables, you reference the variable through the
MySession class's strongly-typed property.
The following are worthy points of note regarding this class:
- The class has
Friend scope. It can't be accessed outside of the Web application. That's just sound security.
Private constructor prevents instantiation. Since all of the other methods are marked
Shared, it makes no sense to instantiate an instance of this class.
Session property returns an instance of the current session. This property is marked
Shared, and completely hides how we're getting the session information.
Exists property returns
True if we have a valid session object. This helps us to avoid calling the various properties on the
MySession class if there isn't a valid session to begin with. This is frequently the case in an exception handler.
UserId property (a sample property only) is marked
Shared, and is responsible for getting a value out of the session and putting it into the session. It uses a class-scope constant to reference the session variable, ensuring that both the getter and setter reference the same session variable. Further, the property is strongly typed. When you call this property, the session variable is already converted to the appropriate data type.
Using a class like this provides a host of benefits:
- The session variables are now centralized in one place. This provides an immediate payback in the form of IntelliSense support in your code editor. Simply type
MySession., and IntelliSense will provide you with a drop-down list of all the available session variables. You'll no longer have to remember what they are, or what the string keys are that reference them.
- The properties are strongly typed. You'll no longer have to manually type-cast the session variables at the point of call. This facilitates the DRY principle: Don't Repeat Yourself. Centralize the type-cast in one place, and be done with it. Depending on the number of session variables you use, and how frequently you use them, this could provide a substantial cleanup of your code.
- The use of a class-level constant to refer to the session keys makes it vastly simpler to rename the session variable if you need to. You can simply change the constant's value, and even leave the property name alone if you need to. However, with modern refactoring tools like RefactorPro, this task is now far easier and safer because you're using a property on a class instead of a string literal.
- You can now document the session variables. Although the sample above only shows a summary description of each item for brevity's sake, the possibilities are endless. You can fully document each session variable, and know where to go to find that documentation in the code. A little clarity goes a long way.
- You can now deprecate a session variable by applying the
ObsoleteAttribute to the property for the variable. Once you do that, the Task List pane will identify every point in the code that references that variable. If you want to refer the caller to a preferred session variable, you can do so with the string argument to the
Exists method allows you test for the existence of a valid session before querying it for information. This is tremendously helpful in avoiding an unexpected
NullReferenceException in your code, particularly in exception handlers and error pages that happen to be .aspx pages.
- Because the session variables are wrapped in properties, you can write the getters to test for variable existence and return a default value if the variable is missing. This is a lot harder to do consistently if you are using string constants sprinkled throughout your code.
You might be wondering where the exception handling code is. There isn't any, and that's by design. If a session variable is missing in a property's getter, it doesn't do me any good to catch that condition and rethrow it--I won't be telling the caller anything that the exception won't already be telling them. My exception handling code in the pages and the fallback exception handler in global.asax are responsible for cleanly handling those exceptions.
It also doesn't do me any good to handle the
NullReferenceException that will be thrown if I try to reference the
Session and it's not there. Again, the global exception handler will take care of it. I could, of course, wrap the exception in a custom
ApplicationException, and you always have that option. Then again, I could always perform the check by calling
MySession.Exists before attempting to retrieve any properties, and avoid the exception altogether.
Points of Interest
For those familiar with the term, I eat my own dog food. I use this class, or one very similar to it, in my own applications, and in production code. Believe me, it saves time, and simplifies application maintenance and development. It reduces the intellectual complexity of the code enormously. Without a centralized "repository" for session variables, you never really know how many of them you have out there. This is especially true when multiple developers are working on the project: you never really know when someone has created a session variable on the fly, and no one else knows about it.
Of course, there's no guarantee that developers will use a class like this religiously once it's put in place. But a periodic search (using Find in Files) for "Session(" that turns up anything outside of the
MySession object should send up red flags and alert you to the fact that something needs to be moved into it.
Encapsulating the details of the session object might not at first seem like it's a big deal. It's just a keyed collection, after all. It's when you start to think about the maintenance details that you realize what a nightmare it can become. When you start worrying about scalability, and realize that you need to pare down that session size, and you don't know where all those variables are, or what they're used for, that it starts to sink in. And when you get tired of casting the same variable to the same data type over and over again, you start writing functions to cast them for you--and you start wondering if someone else on the project hasn't already done so.
It's worth it to do it once, and do it right. A little upfront effort goes a long way, and reduces cost and risk down the road. So think about it, and if it fits your model, implement it. You might be glad you did. I certainly am.
- Initial article -- March 2007
- Added the missing
Shared keyword to the
Exists method -- 3/13/07