Monday, March 12, 2007

Wrap Those Session Variables!

An interesting topic came up on the ASP.NET newsgroup forums today. The question arose: "Is there a standard naming convention for session variables?"

While the debate meandered around the various conventions, and their suitability, and whether or not one even applied to session variables, my chief thought was whether or not one should even see the names of the session variables.

Enter the basic premise of information hiding. The ASP.NET Session object is, essentially a keyed collection. (Let's not bicker about it's internal implementation. List, hash table, whatever. It's essentially a keyed collection, accessed by a string key.)

My point is that the individual keys used to access the items inside should not be replicated throughout your software. What if you choose to change the name of the key? What if the key's name isn't descriptive enough, and needs to be refined?

Sure, you could use a constant, but where do you put it? In a global constants file? That's rather icky. As anyone knows, a global constants file can quickly grow quite large, and navigating it can become a nightmare in and of itself. Then there's the problem of scoping. What if you have two constants with very similar names, and you want them both to have global scope? Now you get to mangle the name of one of them. Good luck choosing.

The ideal solution is to encapsulate the session variables in a class that manages them. This isn't as difficult as it seems, and it provides a host of benefits. The class is relatively straightforward, as shown below.

Option Explicit On
Option Strict On

Imports System.Web.SessionState

Friend NotInheritable Class MySession

Private Const CompanySessionKey As String = "Company"

Private Sub New()
End Sub

Private Shared ReadOnly Property Session As HttpSessionState
Get
Return HttpContext.Current.Session
End Get
End Property

Public ReadOnly Property IsValid() As Boolean
Get
Return Not Session Is Nothing
End Get
End Property

Public Shared Property Company As String
Get
Return DirectCast(Session(CompanySessionKey), String)
End Get
Set(ByVal value As String)
Session(CompanySessionKey)
= value
End Set
End Property

End Class


A few things to note about this class:


  • The class has Friend scope. It can't be accessed outside of the Web application. That's just sound security.
  • The Private constructor prevents instantiation. Since all of the other methods are marked Shared, it makes no sense to instantiate an instance of this class.
  • The Session property returns an instance of the current session. This property is marked Private and Shared, and completely hides how we're getting the session information.
  • The IsValid 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 might be the case in an exception handler.
  • The Company property is marked Public and 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.

Creating this class provides a clean, straightforward way to reference session variables in your code. For example, consider the following (not uncommon) code sample:


lblCompany.Text = DirectCast(Session("company"), String)


Using the class described above, this code is reduced to the following:


lblCompany.Text = MySession.Company


Now, that may not look like much to you now, but imagine what it will save you when you have lots of session variables. And imagine how much easier it will be to refactor those variables should the need arise to do so. 

Finally, you can provide code comments in a centralized place that document what the session variables mean and are used for. That, in and of itself, is a huge boon to productivity. A little clarity goes a long way.

And just to show that I eat my own dog food, I use this very class in my own production software. And it does work, and it does save lots of time. I don't have to remember what that cryptic string is that retrieves a specific session variable. Instead, I type "MySession." and IntelliSense presents me with a list of available session variables.

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 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.IsValid before attempting to retrieve any properties, and avoid the exception altogether.

So there you have it. It's not a hard class to implement, but it pays off pretty well. For a little upfront effort, you get a decent return on your investment. Your code's readability and maintainability improve remarkably, and you know that you can refactor it with a high degree of safety. Further, you can document those session variables in the code, close to the statements that get and set them. And if you decide that you no longer want to use certain session variables, you can easily deprecate them by applying the Obsolete attribute to the properties to quickly identify every point in the code that's using them.

So think about it. And if it's worth your time, implement it. I think you'll be glad you did.

7 comments:

Unknown said...

Well, I didn't understand any of that, seeing as I only code websites. But, you sound like you know what you're doing! Cheers!

Anonymous said...

Great post and great tip! However, I do get the following error on implementing it in a testproject:

'MyClass'is not accessible in this context because it is 'Private'.

Basically, I'm not familiar with the usage of the 'Friend'-scope. Can you give a hint?

Mike Hofer said...

Anonymous,

A Friend class is only visible to other objects within the same assemblies. If you want to use it outside of the assembly, you'll need to make it Public.

Mike Hofer said...

Katie,

If you're coding websites, and doing them in ASP.NET, this artcle should apply. However, as with any article, if you don't understand it, don't bother with it. And certainly never experiment with something in production software! :)

Cheers!

Praful Memane said...

Hi
Its a great way to simplify the much tedious process of dealing with session variables.....
Now the problem is this code if converted in c# it gives me ann error saying
'MySession.Session' is a 'property' but is used like a 'method'.......
any help will be really appreciated..

Praful Memane said...

Hi,
The issue has been resolved...
it should be Session[] n I used Session()... so mad of me...

Jaimir Guerrero said...

Hi,

thanks for sharing your idea.

We implemented it in our project, but when we published our web site on iis 7.0 and two or more users enter concurrently, last user's data overwrite the others users data, mainly when the session variable was use inside of objectdatasource.

could you please help me?,

thanks

Jaimir G.