Replacing Session State
About
I have spent the last few years tinkering with custom Content Management Systems, culminating in the latest version of Ousia.
Previous versions had always used ASP.NET Session State to maintain items between postbacks, or to hold information while a request was happening, in combination with a custom request handler that populates various page route data items.
Session state is a handy way of storing the data, but I had found a few drawbacks on the out of the box functionality;
- If a user was editing a lengthy document, the session could run out before they saved the item
- Restricted to a single IIS process running the entire application.
I had been looking at getting Ousia migrated from a standard worker process to a Web Garden, and knew that I would have to find some alternative way of remembering data cross process. Some of the solutions I had previously used or encountered online included and had various pros and cons;
Options
- HttpContext
- ViewState
- Cookies
- SessionState in SQL Server
About
Logic dictated (my logic anyway) to me that I had to split the purpose of the data in two.
Was it cross-page/cross-request data, or something that I needed to remember for just the time the request took to process?
Request Processing Values – Easy Win
I very quickly realised that there is no need to use SessionState to remember data for the time a request took to process. This was easily handled by HttpContext.
I may use this in complex postback scenarios to set a screen visibility further down the request chain, something along these lines. To make this switch, which I would recommend, then you are looking at something similar to the code below.
VB.NET
Request Processing Values – Easy Win
It was that simple, literally just change the code from Session to Context and half the problems had gone away.
It didn't solve the process of remembering data between postbacks though, that would take more thought.
Cross Request Data
I decided to use cookies to remember the data cross request, but thought I would take it to the next level in terms of storing and accessing the data, by creating a series of functions and subs that would make it easier elsewhere to access this data, cross sub domain.
Domain Function
By default, a cookie created in IIS would be at the full domain level and accessible only to that domain. This wouldn't help with the different subdomains I have on both the Claytabase and gsclayton.net sites. Luckily you can set the cookie domain.
I found that during the writing of the other parts I was re-using this domain functionality, so moved it to a separate function. This was required for setting cookies on internal servers.
VB.NET
Cookie Expiration
Yep, cookies don't come back with an expiry date you sent them out with.
Top Tip – Save the expiration as ticks within the cookie.
VB.NET
Setting Cookie Values
Now two subs are needed to create and set the cookie values. These require both the Request and Response as parameters, a cookie name, domain and timeout in minutes to work.
The setting of the values happens in the second of the subs, and you just pass in the additional Field and Value as strings.
VB.NET
If IsNothing(Request.Cookies(CookieName)) Then
Dim PersistenceCookie As New HttpCookie(CookieName)
Domain = UpdDomain(Domain)
PersistenceCookie.Domain = Domain
PersistenceCookie.Expires = DateAdd(DateInterval.Minute, TimeOut, Now.ToUniversalTime())
PersistenceCookie("Expires") = PersistenceCookie.Expires.Ticks
Response.Cookies.Add(PersistenceCookie)
End If
End Sub
Public Shared Sub PersistenceSet(Request As HttpRequest, Response As HttpResponse, CookieName As String, Domain As String, TimeOut As Integer,
Field As String, Value As String)
If IsNothing(Request.Cookies(CookieName)) Then
PersistenceCreate(Request, Response, CookieName, Domain, TimeOut)
End If
Dim PersistenceCookie As HttpCookie = Request.Cookies(CookieName)
Dim Expires As Long = GetExpires(PersistenceCookie)
Domain = UpdDomain(Domain)
PersistenceCookie.Domain = Domain
If Expires < DateAdd(DateInterval.Minute, TimeOut, Now.ToUniversalTime()).Ticks Then
PersistenceCookie.Expires = DateAdd(DateInterval.Minute, TimeOut, Now.ToUniversalTime())
PersistenceCookie("Expires") = PersistenceCookie.Expires.Ticks
Else
PersistenceCookie.Expires = New DateTime(Expires)
PersistenceCookie("Expires") = PersistenceCookie.Expires.Ticks
End If
PersistenceCookie.Item(Field) = Value
Response.Cookies.Add(PersistenceCookie)
End Sub
Getting Cookie Values
These are two slightly different flavours of the same thing. The first returns a value from the cookie if it exists or an empty string, the second a simple Boolean if the cookie value is equal to the input variable.
VB.NET
If Not IsNothing(Request.Cookies(CookieName)) Then
If Not IsNothing(Request.Cookies(CookieName)(Field)) Then
r = Request.Cookies(CookieName)(Field).ToString
End If
End If
Return r
End Function
Public Shared Function PersistenceCheck(Request As HttpRequest, CookieName As String, Field As String, Value As String) As Boolean
If Not IsNothing(Request.Cookies(CookieName)) Then
If Not IsNothing(Request.Cookies(CookieName)(Field)) Then
If Request.Cookies(CookieName)(Field) = Value Then
r = True
End If
End If
End If
Return r
End Function
Cookie Recycling
Hold on a second! Don't run off with the previous bits just yet. This is where the magic happens.
You will quickly realise that the cookie will just expire unless you keep resetting a value every so often. This is plugged into our request handler to remember our persistence cookie among other things. It simply resets the cookie expiry if it falls below 20 minutes.
If Not IsNothing(HttpContext.Current.Request.Cookies(CookieName)) Then
Dim PersistenceCookie As HttpCookie = HttpContext.Current.Request.Cookies(CookieName)
Dim Expires As Long = GetExpires(PersistenceCookie)
Domain = UpdDomain(Domain)
If Expires < DateAdd(DateInterval.Minute, 20, Now.ToUniversalTime()).Ticks Then
PersistenceCookie.Expires = DateAdd(DateInterval.Minute, 20, Now.ToUniversalTime())
PersistenceCookie("Expires") = PersistenceCookie.Expires.Ticks
PersistenceCookie.Domain = Domain
Response.Cookies.Add(PersistenceCookie)
End If
End If
End Sub
Recap and Usage
Using the functions couldn't be simpler. A simple example may be to set a dropdown field selected value for the next time the user came back. In this scenario we would use it as follows.
To test, lets add a create a new web form with a simple dropdown, set to auto postback on change.
HMTL
<asp:ListItem Value="2">Value 2</asp:ListItem>
<asp:ListItem Value="3">Value 3</asp:ListItem>
<asp:ListItem Value="4">Value 4</asp:ListItem>
</asp:dropdownlist>
VB.NET
Protected Sub CookieDrop_SelectedIndexChanged(sender As Object, e As EventArgs) Handles CookieDrop.SelectedIndexChanged
'Set the cookie for one minute...
PersistenceSet(Request, Response, "DropDownTest", "", 1, "SelectedValue", CookieDrop.SelectedValue)
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
GetData()
PersistenceRecycle(Request, Response, "DropDownTest", "")
If Not IsPostBack Then 'Delete if you want to remove ViewState!
SetDropDownVal()
End If
End Sub
Protected Sub Page_PreRender() Handles Me.PreRender
End Sub
Protected Sub GetData()
'Bind Data to DropDown
End Sub
Protected Sub SetDropDownVal()
Dim DropDownVal As Integer
If PersistenceGet(Request, "DropDownTest", "SelectedValue") <> "" Then
If IsNumeric(PersistenceGet(Request, "DropDownTest", "SelectedValue")) Then
DropDownVal = PersistenceGet(Request, "DropDownTest", "SelectedValue")
Try
CookieDrop.SelectedValue = DropDownVal
Catch
End Try
End If
End If
End Sub
End Class
Website design by Claytabase
This is a section of code that has been modified from Ousia Content Management System code, one of the quickest and most optimised systems on the market, part of our website design services.
These are available with sites starting at around £500.