Here's the situation you are developing an ASP.NET application and you need to maintain server side state. You start to use HttpSessionState and it's quite straight forward. Basically you add code like Session("foo") = obj where foo is the key and obj is the object you what to store in session state. Everything works fine, all unit and integration tests succeed and then you deploy to production and it no longer works. After hours of investigation you find out that the production environment is configured to use out of process session state. The sirens go off and you realize that you forgot to make one of the objects serializable. In process session state does not require that objects be serializable, whereas, out of process does.
Why not be proactive and come up with an approach that will detect this before it hits production. There are a number of approaches that you can use;
- Always use out of process session state. This would work but may not be practical.
- Add a FxCop rule to ensure all objects placed in session are serializable.
- Write a wrapper class that checks that objects placed into session state are serializable.
The last approach is the subject of this post. Listing 1 shows the code for the session wrapper.
Listing 1: Session Wrapper Class
'Access to a singleton of type SerializableSession.
Public Module MyGlobals
Public Session As SerializableSession = Singleton(Of SerializableSession).Instance
End Module
'A simple class wrapper for session. I've only exposed Item (default property).
'Feel free to expose additional methods (e.g., Add)
Public Class SerializableSession
Default Public Property Item(ByVal name As String) As Object
Get
Return HttpContext.Current.Session(name)
End Get
Set(ByVal value As Object)
If IsSerializable(value) Then
HttpContext.Current.Session(name) = value
Else
Throw New Exception(value.GetType.FullName + " is not serializable")
End If
End Set
End Property
'Helper function to determine whether the object passed in is serializable.
'Right now only object that are classes or value types are supported.
'If you plan to pass references to interfaces or enums then you may not
'modify this function.
Private Function IsSerializable(ByVal o As Object) As Boolean
Dim t As Type = o.GetType
Dim IsObjectSerializable As Boolean = False
If t.IsClass Then
'Check to see if ISerializable interface is implemented.
For Each [interface] In t.GetInterfaces
If [interface] Is GetType(System.Runtime.Serialization.ISerializable) Then
IsObjectSerializable = True
Exit For
End If
Next
'Check to see if object is decorated with the Serializable attribute
If Not IsObjectSerializable Then
For Each [attribute] In t.GetCustomAttributes(True)
If [attribute].GetType Is GetType(System.SerializableAttribute) Then
IsObjectSerializable = True
Exit For
End If
Next
End If
Else
IsObjectSerializable = True
End If
Return IsObjectSerializable
End Function
End Class
The above code makes use of the generic singleton from a previous post. The SerializableSession class only exposes a single property called Item. You can easily add to this class and expose additional methods that exist in the HttpSessionState class. The Item setter calls the IsSerializable helper function that checks that the object is a class and if so that the class implements ISerializable or is decorated with the Serializable attribute. If these conditions are meet then the function returns True. If the object does meet these criterion then False is returned and the setter throws an exception.
This code is by no means complete. What if an interface is passed to the setter of SerializableSession? How should it deal with it? I'll leave that to the reader of this post to figure out.
The Module MyGlobals provides a convenience variable that creates a singleton of type SerializableSession. Listing 2 shows how to make calls to this session wrapper class.
Listing 2: Using the Session Wrapper Class
Partial Class _Default
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.Load
MyGlobals.Session("foobar") = New Foobar()
MyGlobals.Session("moobar") = New Moobar()
MyGlobals.Session("int") = 5
Response.Write(MyGlobals.Session("foobar").GetType.ToString + ":" + _
CType(MyGlobals.Session("foobar"), Foobar).val + "</br>")
Response.Write(MyGlobals.Session("moobar").GetType.ToString + ":" + _
CType(MyGlobals.Session("moobar"), Moobar).val + "</br>")
Response.Write(MyGlobals.Session("int").GetType.ToString + ":" + _
CType(MyGlobals.Session("int"), Integer).ToString + "</br>")
End Sub
End Class
'Simple test classes. Both Foobar and Moobar should work fine.
Public Class Foobar
Implements System.Runtime.Serialization.ISerializable
Public val As String = "Foobar-String"
Public Sub GetObjectData(ByVal info As System.Runtime.Serialization.SerializationInfo, ByVal context As System.Runtime.Serialization.StreamingContext) Implements System.Runtime.Serialization.ISerializable.GetObjectData
End Sub
End Class
<Serializable()> _
Public Class Moobar
Public val As String = "Moobar-String"
End Class
'If you try to add this to session using SerializableSession a runtime exception will be
'thrown.
Public Class xFoobar
Sub New()
End Sub
Public Sub foo()
End Sub
End Class
Objects that are not serializable will be caught at runtime, regardless of which environment the code is running on. This is a step in the right direction but we can do better; how about generating compile time errors. I will need to give it some thought as how that might be implemented. Same bat channel, same bat time perhaps in a future post!
Guess the movie
I think the message to, uh, psychos, fanatics, murderers, nutcases all over the world is, uh, "do not mess with suburbanites". Because, uh, frankly we're just not gonna take it any more. Ya know, we're not gonna be content to look after our lawns and wax our cars, paint out houses. We're out to get them, Don, we are out to get them.