We all know that in ASP.NET (up to and including version 3.5), accessing session data was done via an indexer on the HttpSessionState
object. For example:
Session["MyInt"] = 12;
int myInt = (int)Session["MyInt"];
Session("MyInt") = 12
Dim myInt as Integer = CInt(Session("MyInt"))
The System.Dynamic
namespace (introduced in .NET 4.0) gives us the ability to create objects whose members (properties, methods, etc.) are not specifically coded; but instead are added dynamically as and when they are required. This means we should now be able to access session information via a dynamic object where, instead of an indexer, we use a dynamic property to specify the name of the session item.
We can already see examples of where this has been done elsewhere in the framework. Take, for example, ViewData
in MVC:
ViewData["MyString"] = "Foo";
ViewBag.MyString = "Foo";
ViewData("MyString") = "Foo"
ViewBag.MyString = "Foo"
So how can we achieve the same with ASP.NET session data? Firstly, we need to create ourselves a dynamic object which is going to wrap the current HttpSessionState
object:
public sealed class SessionBag : DynamicObject
{
private SessionBag()
{
}
}
Public NotInheritable Class SessionBag
Inherits DynamicObject
Private Sub New()
End Sub
End Class
Note how the class inherits from DynamicObject
. This is a base class for specifying dynamic behaviour at run time. Additionally, the constructor is set as private
. The reason for this will become apparent later.
Next, we add a convenience property for accessing the current HttpSessionState
object:
private HttpSessionStateBase Session
{
get { return new HttpSessionStateWrapper(HttpContext.Current.Session); }
}
Private ReadOnly Property Session As HttpSessionStateBase
Get
Return New HttpSessionStateWrapper(HttpContext.Current.Session)
End Get
End Property
We then override the TryGetMember()
and TrySetMember()
methods of DynamicObject
. These methods define how our dynamic object should behave when a dynamic property is accessed. In this case, we want it to retrieve and add items to the HttpSessionState
:
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = Session[binder.Name];
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
Session[binder.Name] = value;
return true;
}
Public Overrides Function TryGetMember(binder As _
System.Dynamic.GetMemberBinder, ByRef result As Object) As Boolean
result = Session(binder.Name)
Return True
End Function
Public Overrides Function TrySetMember(binder As _
System.Dynamic.SetMemberBinder, value As Object) As Boolean
Session(binder.Name) = value
Return True
End Function
Additionally, we can override the TryGetIndex()
and TrySetIndex()
methods so our session data is still accessible using its index position:
public override bool TryGetIndex(GetIndexBinder binder,
object[] indexes, out object result)
{
int index = (int)indexes[0];
result = Session[index];
return result != null;
}
public override bool TrySetIndex(SetIndexBinder binder,
object[] indexes, object value)
{
int index = (int)indexes[0];
Session[index] = value;
return true;
}
Public Overrides Function TryGetIndex(binder As _
System.Dynamic.GetIndexBinder, indexes() As Object, _
ByRef result As Object) As Boolean
Dim index As Integer = CInt(indexes(0))
result = Session(index)
Return Not result Is Nothing
End Function
Public Overrides Function TrySetIndex(binder As _
System.Dynamic.SetIndexBinder, indexes() As Object, _
value As Object) As Boolean
Dim index As Integer = CInt(indexes(0))
Session(index) = value
Return True
End Function
Finally, we add a static
variable for the current SessionBag
object and a convenience property for accessing it. This ensures that only one SessionBag
object is created and is why its constructor is private
:
private static readonly SessionBag sessionBag;
static SessionBag()
{
sessionBag = new SessionBag();
}
public static dynamic Current
{
get { return sessionBag; }
}
Private Shared ReadOnly _sessionBag As SessionBag
Shared Sub New()
_sessionBag = New SessionBag()
End Sub
Public Shared ReadOnly Property Current As Object
Get
Return _sessionBag
End Get
End Property
Note that in C#, the return
type of the Current
property is of type dynamic
. This tells the C# compiler to use late binding on that object and allow members to be added dynamically. The same thing is achieved in Visual Basic by setting Option Strict
to Off
.
The complete code for the class is as follows:
public sealed class SessionBag : DynamicObject
{
private static readonly SessionBag sessionBag;
static SessionBag()
{
sessionBag = new SessionBag();
}
private SessionBag()
{
}
private HttpSessionStateBase Session
{
get { return new HttpSessionStateWrapper(HttpContext.Current.Session); }
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = Session[binder.Name];
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
Session[binder.Name] = value;
return true;
}
public override bool TryGetIndex(GetIndexBinder
binder, object[] indexes, out object result)
{
int index = (int)indexes[0];
result = Session[index];
return result != null;
}
public override bool TrySetIndex(SetIndexBinder binder,
object[] indexes, object value)
{
int index = (int)indexes[0];
Session[index] = value;
return true;
}
public static dynamic Current
{
get { return sessionBag; }
}
}
Public NotInheritable Class SessionBag
Inherits DynamicObject
Private Shared ReadOnly _sessionBag As SessionBag
Shared Sub New()
_sessionBag = New SessionBag()
End Sub
Private Sub New()
End Sub
Private ReadOnly Property Session As HttpSessionStateBase
Get
Return New HttpSessionStateWrapper(HttpContext.Current.Session)
End Get
End Property
Public Overrides Function TryGetMember(binder As _
System.Dynamic.GetMemberBinder, ByRef result As Object) As Boolean
result = Session(binder.Name)
Return True
End Function
Public Overrides Function TrySetMember(binder As _
System.Dynamic.SetMemberBinder, value As Object) As Boolean
Session(binder.Name) = value
Return True
End Function
Public Overrides Function TryGetIndex(binder As _
System.Dynamic.GetIndexBinder, indexes() As Object, _
ByRef result As Object) As Boolean
Dim index As Integer = CInt(indexes(0))
result = Session(index)
Return Not result Is Nothing
End Function
Public Overrides Function TrySetIndex(binder As _
System.Dynamic.SetIndexBinder, indexes() _
As Object, value As Object) As Boolean
Dim index As Integer = CInt(indexes(0))
Session(index) = value
Return True
End Function
Public Shared ReadOnly Property Current As Object
Get
Return _sessionBag
End Get
End Property
End Class
An Example
Here is an example of our SessionBag
class in action. It is a simple MVC application which generates random numbers and uses the ASP.NET session to output the current number and the last number to the browser:
C#
public class RandomController : Controller
{
private Random random;
public RandomController()
{
random = new Random();
}
public ActionResult Index()
{
SessionBag.Current.LastNumber = SessionBag.Current.CurrentNumber;
int number = random.Next();
SessionBag.Current.CurrentNumber = number;
return View();
}
}
ASP.NET
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<!DOCTYPE html>
<html>
<head runat="server">
<title>Random</title>
</head>
<body>
<div>
<% using (Html.BeginForm())
{ %>
<% if (SessionBag.Current.CurrentNumber != null)
{%>
Current number is
<%: SessionBag.Current.CurrentNumber %>
<br />
<%} %>
<% if (SessionBag.Current.LastNumber != null)
{%>
Last number was
<%: SessionBag.Current.LastNumber %>
<br />
<%} %>
<input type="submit" value="Generate" />
<%} %>
</div>
</body>
</html>
VB.NET
Public Class RandomController
Inherits System.Web.Mvc.Controller
Private _random As Random
Public Sub New()
_random = New Random()
End Sub
Public Function Index() As ActionResult
SessionBag.Current.LastNumber = SessionBag.Current.CurrentNumber
Dim number As Integer = _random.Next()
SessionBag.Current.CurrentNumber = number
Return View()
End Function
End Class
ASP.NET
<%@ Page Language="VB" Inherits="System.Web.Mvc.ViewPage" %>
<!DOCTYPE html>
<html>
<head runat="server">
<title>Random</title>
</head>
<body>
<div>
<%: "" %>
<% Using Html.BeginForm()
%>
<% If Not SessionBag.Current.CurrentNumber Is Nothing Then
%>
Current number is
<%: SessionBag.Current.CurrentNumber %>
<br />
<%
End If%>
<% If Not SessionBag.Current.LastNumber Is Nothing Then
%>
Last number was
<%: SessionBag.Current.LastNumber %>
<br />
<%
End If%>
<input type="submit" value="Generate" />
<%
End Using%>
</div>
</body>
</html>