Session State Providers

 

Introduction to the Provider Model
Membership Providers
Role Providers
Site Map Providers
Session State Providers
Profile Providers
Web Event Providers
Web Parts Personalization Providers
Custom Provider-Based Services
Hands-on Custom Providers: The Contoso Times

Session state providers provide the interface between ASP.NET session state and session state data sources. The two most common reasons for writing a custom session state provider are:

  • You wish to store session state in a data source that is not supported by the session state providers included with the .NET Framework, such as an Oracle database.
  • You wish to store session state in a SQL Server database whose schema differs from that of the database used by System.Web.SessionState.SqlSessionStateStore.

Core ASP.NET session state services are provided by System.Web.SessionState.SessionStateModule, instances of which are referred to as session state modules. Session state modules encapsulate session state in instances of System.Web.SessionState.SessionStateStoreData, allocating one SessionStateStoreData per session (per user). The fundamental job of a session state provider is to serialize SessionStateDataStores to session state data sources and deserialize them on demand. SessionStateDataStore has three properties that must be serialized in order to hydrate class instances:

  • Items, which encapsulates a session's non-static objects
  • StaticObjects, which encapsulates a session's static objects
  • Timeout, which specifies the session's time-out (in minutes)

Items and StaticObjects can be serialized and deserialized easily enough by calling their Serialize and Deserialize methods. The Timeout property is a simple System.Int32 and is therefore also easily serialized and deserialized. Thus, if sssd is a reference to an instance of SessionStateStoreData, the core logic to write the contents of a session to a session state data source is often no more complicated than this:

stream1 = new MemoryStream ()
stream2 = new MemoryStream ()
writer1 = new BinaryWriter (stream1)
writer2 = new BinaryWriter (stream2)

' Serialize Items and StaticObjects
CType(sssd.Items, SessionStateItemCollection).Serialize (writer1)
sssd.StaticObjects.Serialize (writer2)

' Convert serialized items into byte arrays
Dim items As Byte() = stream1.ToArray ()
Dim statics As Byte() = stream2.ToArray ()
Dim timeout As Integer = sssd.Timeout

' TODO: Write items, statics, and timeout to the data source

One of the challenges to writing a session state provider is implementing a locking mechanism that prevents a given session from being accessed by two or more concurrent requests. That mechanism ensures the consistency of session state data by preventing one request from reading a session at the same time that another request is writing it. The locking mechanism must work even if the session state data source is a remote resource shared by several Web servers. Locking behavior is discussed in Synchronizing Concurrent Accesses to a Session.

Another consideration to take into account when designing a session state provider is whether to support expiration callbacks notifying SessionStateModule when sessions time out. Expiration callbacks are discussed in Expiration Callbacks and Session_End Events.

The SessionStateStoreProviderBase Class

Developers writing custom session state providers begin by deriving from System.Web.SessionState.SessionStateStoreProviderBase, which derives from ProviderBase and adds mustoverride methods defining the basic characteristics of a session state provider. SessionStateStoreProviderBase is prototyped as follows:

Public MustInherit Class SessionStateStoreProviderBase 
Inherits ProviderBase

Public MustOverride Sub Dispose()

Public MustOverride Function SetItemExpireCallback( _
   ByVal expireCallback As SessionStateItemExpireCallback) As Boolean

Public MustOverride Sub InitializeRequest(ByVal context As HttpContext)

Public MustOverride Function GetItem(ByVal context As HttpContext, _
   ByVal id As String, ByRef locked As Boolean, _
   ByRef lockAge As TimeSpan, ByRef lockId As Object, _
   ByRef actions As SessionStateActions) As SessionStateStoreData

Public MustOverride Function GetItemExclusive( _
   ByVal context As HttpContext, ByVal id As String, _
   ByRef locked As Boolean, ByRef lockAge As TimeSpan, _
   ByRef lockId As Object, _
   ByRef actions As SessionStateActions) As SessionStateStoreData

Public MustOverride Sub ReleaseItemExclusive( _
ByVal context As HttpContext, _
   ByVal id As String, ByVal lockId As Object)

Public MustOverride Sub SetAndReleaseItemExclusive( _
   ByVal context As HttpContext, ByVal id As String, _
   ByVal item As SessionStateStoreData, ByVal lockId As Object, _
   ByVal newItem As Boolean)

Public MustOverride Sub RemoveItem(ByVal context As HttpContext, _
   ByVal id As String, ByVal lockId As Object, _
   ByVal item As SessionStateStoreData)

Public MustOverride Sub ResetItemTimeout( _
   ByVal context As HttpContext, ByVal id As String)

Public MustOverride Function CreateNewStoreData( _
   ByVal context As HttpContext,
   ByVal timeout As Integer) As SessionStateStoreData

Public MustOverride Sub CreateUninitializedItem( _
   ByVal context As HttpContext, _
   ByVal id As String, ByVal timeout As Integer)

Public MustOverride Sub EndRequest(ByVal context As HttpContext)

End Class

The following table describes SessionStateStoreProviderBase's methods and provides helpful notes regarding their implementation:

Method Description
CreateNewStoreData Called to create a SessionStateStoreData for a new session.
CreateUninitializedItem Called to create a new, uninitialized session in the data source. Called by SessionStateModule when session state is cookieless to prevent the session from being unrecognized following a redirect.
Dispose Called when the provider is disposed of to afford it the opportunity to perform any last-chance cleanup.
EndRequest Called in response to EndRequest events to afford the provider the opportunity to perform any per-request cleanup.
GetItem If session state is read-only (that is, if the requested page implements IReadOnlySessionState), called to load the SessionStateStoreData corresponding to the specified session ID and apply a read lock so other requests can read the SessionStateDataStore but not modify it until the lock is released.

If the specifed session doesn't exist in the data source, this method returns Nothing and sets the ByRef parameter named locked to false. SessionStateModule then calls CreateNewStoreData to create a new SessionStateStoreData to serve this session.

If the specified session is currently locked, this method returns Nothing and sets the ByRef parameter named locked to true, causing SessionStateModule to retry GetItem at half-second intervals until the lock comes free or times out. In addition to setting locked to true, this method also returns the lock age and lock ID using the lockAge and lockId parameters.

GetItemExclusive If session state is read-write (that is, if the requested page implements IRequiresSessionState), called to load the SessionStateStoreData corresponding to the specified session ID and apply a write lock so other requests can neither read nor write the SessionStateStoreData until the lock is released.

If the specifed session doesn't exist in the data source, this method returns Nothing and sets the out parameter named locked to false. SessionStateModule then calls CreateNewStoreData to create a new SessionStateStoreData to serve this session.

If the specified session is currently locked, this method returns Nothing and sets the out parameter named locked to true, causing SessionStateModule to retry GetItemExclusive at half-second intervals until the lock comes free or times out. In addition to setting locked to true, this method also returns the lock age and lock ID using the lockAge and lockId parameters.

InitializeRequest Called in response to AcquireRequestState events to afford the provider the opportunity to perform any per-request initialization.
ReleaseItemExclusive Called to unlock the specified session if a request times out waiting for the lock to come free.
RemoveItem Called to remove the specified session from the data source.
ResetItemTimeout Called to reset the expiration time of the specified session.
SetAndReleaseItemExclusive Called to write modified session state to the data source. The newItem parameter indicates whether the supplied SessionStateStoreData corresponds to an existing session in the data source or a new one. If newItem is true, SetAndReleaseItemExclusive adds a new session to the data source. Otherwise, it updates an existing one.
SetItemExpireCallback Called to supply the provider with a callback method for notifying SessionStateModule that a session has expired. If the provider supports session expiration, it should return true from this method and notify ASP.NET when sessions expire by calling the supplied callback method. If the provider does not support session expiration, it should return false from this method.

Your job in implementing a custom session state provider in a derived class is to override and provide implementations of SessionStateStoreProviderBase's abstract members, and optionally to override key virtuals such as Initialize.

Synchronizing Concurrent Accesses to a Session

ASP.NET applications are inherently multithreaded. Because requests that arrive in parallel are processed on concurrent threads drawn from a thread pool, it's possible that two or more requests targeting the same session will execute at the same time. (The classic example is when a page contains two frames, each targeting a different ASPX in the same application, causing the browser to submit overlapping requests for the two pages.) To avoid data collisions and erratic behavior, the provider "locks" the session when it begins processing the first request, causing other requests targeting the same session to wait for the lock to come free.

Because there's no harm in allowing concurrent requests to perform overlapping reads, the lock is typically implemented as a reader/writer lock-that is, one that allows any number of threads to read a session but that prevents overlapping reads and writes as well as overlapping writes.

Which brings up two very important questions:

  1. How does a session state provider know when to apply a lock?
  2. How does the provider know whether to treat a request as a reader or a writer?

If the requested page implements the IRequiresSessionState interface (by default, all pages implement IRequiresSessionState), ASP.NET assumes that the page requires read/write access to session state. In response to the AcquireRequestState event fired from the pipeline, SessionStateModule calls the session state provider's GetItemExclusive method. If the targeted session isn't already locked, GetItemExclusive applies a write lock and returns the requested data along with a lock ID (a value that uniquely identifies the lock). However, if the session is locked when GetItemExclusive is called, indicating that another request targeting the same session is currently executing, GetItemExclusive returns Nothing and uses the ByRef parameters passed to it to return the lock ID and the lock's age (how long, in seconds, the session has been locked).

If the requested page implements the IReadOnlySessionState interface instead, ASP.NET assumes that the page reads but does not write session state. (The most common way to implement IReadOnlySessionState is to include an EnableSessionState="ReadOnly" attribute in the page's @ Page directive.) Rather than call the provider's GetItemExclusive method to retrieve the requestor's session state, ASP.NET calls GetItem instead. If the targeted session isn't locked by a writer when GetItem is called, GetItem applies a read lock and returns the requested data. (The read lock ensures that if a read/write request arrives while the current request is executing, it waits for the lock to come free. This prevents read/write requests from overlapping with read requests that are already executing.) Otherwise, GetItem returns Nothing and uses the ByRef parameters passed to it to return the lock ID and the lock's age-just like GetItemExclusive.

The third possiblity—that the page implements neither IRequiresSessionState nor IReadOnlySessionState—tells ASP.NET that the page doesn't use session state, in which case SessionStateModule calls neither GetItem nor GetItemExclusive. The most common way to indicate that a page should implement neither interface is to include an EnableSessionState="false" attribute in the page's @ Page directive.

If SessionStateModule encounters a locked session when it calls GetItem or GetItemExclusive (that is, if either method returns null), it rerequests the data at half-second intervals until the lock is released or the request times out. If a time-out occurs, SessionStateModule calls the provider's ReleaseItemExclusive method to release the lock and allow the session to be accessed.

SessionStateModule identifies locks using the lock IDs returned by GetItem and GetItemExclusive. When SessionStateModule calls SetAndReleaseItemExclusive or ReleaseItemExclusive, it passes in a lock ID. Due to the possibility that a call to ReleaseItemExclusive on one thread could free a lock just before another thread calls SetAndReleaseItemExclusive, the SetAndReleaseItemExclusive method of a provider that supports multiple locks IDs per session should only write the session to the data source if the lock ID input to it matches the lock ID in the data source.

Expiration Callbacks and Session_End Events

After loading a session state provider, SessionStateModule calls the provider's SetItemExpireCallback method, passing in a SessionStateItemExpireCallback delegate that enables the provider to notify SessionStateModule when a session times out. If the provider supports expiration callbacks, it should save the delegate and return true from SetItemExpireCallback. Then, whenever a session times out, the provider should notify SessionStateModule that a session has expired by calling the callback method encapsulated in the delegate. This enables SessionStateModule to fire Session_End events when sessions expire.

Session state providers aren't required to support expiration callbacks. A provider that doesn't support them should return false from SetItemExpireCallback. In that case, SessionStateModule will not be notified when a session expires and will not fire Session_End events.

Inside the ASP.NET Team
How do the built-in session state providers handle expiration callbacks? The in-process session state provider, InProcSessionStateStore, stores session content in the ASP.NET application cache and takes advantage of the cache's sliding-expiration feature to expire sessions when they go for a specified period of time without being accessed. When the provider is notified via a cache removal callback that a session expired from the cache, it notifies SessionStateModule, and SessionStateModule fires a Session_End event.

The other two built-in providers—OutOfProcSessionStateStore and SqlSessionStateStore—don't support expiration callbacks. Both return false from SetItemExpireCallback. OutOfProcSessionStateStore uses the application cache to store sessions, but since session data is stored in a remote process (the "state server" process), the provider doesn't attempt to notify SessionStateModule when a session expires. SqlSessionStateStore relies on a SQL Server agent to "scavenge" the session state database and clean up expired sessions. Having the agent notify the provider about expired sessions so the provider could, in turn, notify SessionStateModule would be a tricky endeavor indeed-especially in a Web farm.

Cookieless Sessions

ASP.NET supports two different types of sessions: cookied and cookieless. The term actually refers to the mechanism used to round-trip session IDs between clients and Web servers and does not imply any difference in the sessions themselves. Cookied sessions round-trip session IDs in HTTP cookies, while cookieless sessions embed session IDs in URLs using a technique known as "URL munging."

In order to support cookieless sessions, a session state provider must implement a CreateUninitializedItem method that creates an uninitialized session. When a request arrives and session state is configured with the default settings for cookieless mode (for example, when the <sessionState> configuration element contains cookieless="UseUri" and regenerateExpiredSessionId="true" attributes), SessionStateModule creates a new session ID, munges it onto the URL, and passes it to CreateUninitializedItem. Afterwards, a redirect occurs with the munged URL as the target. The purpose of calling CreateUninitializedItem is to allow the session ID to be recognized as a valid ID following the redirect. (Otherwise, SessionStateModule would think that the ID extracted from the URL following the redirect represents an expired session, in which case it would generate a new session ID, which would force another redirect and result in an endless loop.) If sessions are cookied rather than cookieless, the provider's CreateUninitializedItem method is never called. When testing a custom session state provider, be certain to test it in both cookied and cookieless mode.

A CreateUninitializedItem implementation can use any technique it desires to ensure that the session ID passed to it is recognized as a valid ID following a redirect. ASP.NET's InProcSessionStateStore provider, for example, inserts an empty SessionStateStoreData (that is, a SessionStateStoreData object with null Items and StaticObjects properties) into the application cache accompanied by a flag marking it as an uninitialized session. SqlSessionStateStore acts similarly, adding a row representing the session to the session state database and flagging it as an uninitialized session.

When a session state provider's GetItem or GetItemExclusive method is called, it returns a SessionStateActions value through the ByRef parameter named actions. The value returned depends on the state of the session identified by the supplied ID. If the data corresponding to the session doesn't exist in the data source or if it exists and is already initialized (that is, if the session was not created by CreateUninitializedItem), GetItem and GetItemExclusive should return SessionStateActions.None through the actions parameter. However, if the session data exists in the data source but is not initialized (indicating the session was created by CreateUninitializedItem), GetItem and GetItemExclusive should return SessionStateActions.InitializeItem. SessionStateModule responds to a SessionStateActions.InitializeItem flag by firing a Session_Start event signifying the start of a new session. It also raises a Session_Start event if GetItem or GetItemExclusive returns SessionStateActions.None following the creation of a new session.

TextFileSessionStateProvider

Listing 1 contains the source code for a SessionStateStoreProviderBase-derivative named TextFileSessionStateProvider that demonstrates the basics of custom session state providers. TextFileSessionStateProvider stores session state in text files named SessionID_Session.txt in the application's ~/App_Data/Session_Data directory, where SessionID is the ID of the corresponding session. Each file contains the state for a specific session and consists of either two or four lines of text:

  • A "0" or "1" indicating whether the session is initialized, where "1" means the session is initialized, and "0" means it is not
  • If line 1 contains a "1", a base-64 string containing the session's serialized non-static objects
  • If line 1 contains a "1", a base-64 string containing the session's serialized static objects
  • A numeric string specifying the session's time-out in minutes

Thus, a file containing an initialized session contains four lines of text, and a file containing an uninitialized session-that is, a session created by CreateUninitializedItem in support of cookieless session state-contains two. You must create the ~/App_Data/Session_Data directory before using the provider; the provider doesn't attempt to create the directory if it doesn't exist. In addition, the provider must have read/write access to the ~/App_Data/Session_Data directory.

Listing 1. TextFileSessionStateProvider

Imports System
Imports System.Configuration
Imports System.Web
Imports System.Web.Security
Imports System.Web.UI
Imports System.Web.SessionState
Imports System.Collections.Specialized
Imports System.Collections.Generic
Imports System.Configuration.Provider
Imports System.Security.Permissions
Imports System.Web.Hosting
Imports System.IO

Public Class TextFileSessionStateProvider
    Inherits SessionStateStoreProviderBase
    Private _sessions As Dictionary(Of String, FileStream) = _
        New Dictionary(Of String, FileStream)()

    Public Overloads Overrides Sub Initialize(ByVal name As String, _
        ByVal config As NameValueCollection)
        ' Verify that config isn't null
        If config Is Nothing Then
            Throw New ArgumentNullException("config")
        End If

        ' Assign the provider a default name if it doesn’t have one
        If String.IsNullOrEmpty(name) Then
            name = "TextFileSessionStateProvider"
        End If

        ' Add a default "description" attribute to config if the
        ' attribute doesn’t exist or is empty
        If String.IsNullOrEmpty(config("description")) Then
            config.Remove("description")
            config.Add("description", _
            "Text file session state provider")
        End If

        ' Call the base class’s Initialize method
        MyBase.Initialize(name, config)

        ' Throw an exception if unrecognized attributes remain
        If config.Count > 0 Then
            Dim attr As String = config.GetKey(0)
            If (Not String.IsNullOrEmpty(attr)) Then
                Throw New _
                ProviderException("Unrecognized attribute: " & attr)
            End If
        End If

        ' Make sure we can read and write files in the
        ' ~/App_Data/Session_Data directory
        Dim permission As FileIOPermission = _
            New FileIOPermission(FileIOPermissionAccess.AllAccess, _
            HttpContext.Current.Server.MapPath( _
            "~/App_Data/Session_Data"))
        permission.Demand()
    End Sub

    Public Overrides Function CreateNewStoreData( _
        ByVal context As HttpContext, _
        ByVal timeout As Integer) As SessionStateStoreData
        Return New SessionStateStoreData( _
            New SessionStateItemCollection(), _
            SessionStateUtility.GetSessionStaticObjects(context), _
            timeout)
    End Function

    Public Overrides Sub CreateUninitializedItem( _
        ByVal context As HttpContext, _
        ByVal id As String, ByVal timeout As Integer)
        ' Create a file containing an uninitialized flag
        ' and a time-out
        Dim writer As StreamWriter = Nothing

        Try
            writer = _
                New StreamWriter( _
                    context.Server.MapPath(GetSessionFileName(id)))
            writer.WriteLine("0")
            writer.WriteLine(timeout.ToString())
        Finally
            If Not writer Is Nothing Then
                writer.Close()
            End If
        End Try
    End Sub

    Public Overrides Function GetItem(ByVal context As HttpContext, _
        ByVal id As String, ByRef locked As Boolean, _
        ByRef lockAge As TimeSpan, ByRef lockId As Object, _
        ByRef actions As SessionStateActions _
        ) As SessionStateStoreData
        Return GetSession(context, id, locked, lockAge, _
        lockId, actions, False)
    End Function

    Public Overrides Function GetItemExclusive( _
        ByVal context As HttpContext, _
        ByVal id As String, ByRef locked As Boolean, _
        ByRef lockAge As TimeSpan, ByRef lockId As Object, _
        ByRef actions As SessionStateActions _
        ) As SessionStateStoreData
        Return GetSession(context, id, locked, lockAge, _
        lockId, actions, True)
    End Function

    Public Overrides Sub SetAndReleaseItemExclusive( _
        ByVal context As HttpContext, ByVal id As String, _
        ByVal item As SessionStateStoreData, _
        ByVal lockId As Object, ByVal newItem As Boolean)
        ' Serialize the session
        Dim items As Byte() = Nothing
        Dim statics As Byte() = Nothing
        SerializeSession(item, items, statics)
        Dim serializedItems As String = _
                            Convert.ToBase64String(items)
        Dim serializedStatics As String = _
                            Convert.ToBase64String(statics)

        ' Get a FileStream representing the session state file
        Dim stream As FileStream = Nothing

        Try
            If newItem Then
                stream = File.Create( _
                    context.Server.MapPath(GetSessionFileName(id)))
            Else
                stream = _sessions(id)
                stream.SetLength(0)
                stream.Seek(0, SeekOrigin.Begin)
            End If

            ' Write session state to the file        
            Dim writer As StreamWriter = Nothing

            Try
                writer = New StreamWriter(stream)
                writer.WriteLine("1") ' Initialized flag
                writer.WriteLine(serializedItems)
                writer.WriteLine(serializedStatics)
                writer.WriteLine(item.Timeout.ToString())
            Finally
                If Not writer Is Nothing Then
                    writer.Close()
                End If
            End Try
        Finally
            If newItem AndAlso Not stream Is Nothing Then
                stream.Close()
            End If
        End Try

        ' Unlock the session
        ReleaseItemExclusive(context, id, lockId)
    End Sub

    Public Overrides Sub ReleaseItemExclusive( _
        ByVal context As HttpContext, ByVal id As String, _
        ByVal lockId As Object)
        ' Release the specified session by closing the corresponding
        ' FileStream and deleting the lock file
        Dim stream As FileStream = Nothing

        If _sessions.TryGetValue(id, stream) Then
            _sessions.Remove(id)
            ReleaseLock(context, CStr(lockId))
            stream.Close()
        End If
    End Sub

    Public Overrides Sub ResetItemTimeout( _
        ByVal context As HttpContext, ByVal id As String)
        ' Update the time stamp on the session state file
        Dim path As String = context.Server.MapPath( _
            GetSessionFileName(id))
        File.SetCreationTime(path, DateTime.Now)
    End Sub

    Public Overrides Sub RemoveItem( _
        ByVal context As HttpContext, _
        ByVal id As String, ByVal lockId As Object, _
        ByVal item As SessionStateStoreData)
        ' Make sure the session is unlocked
        ReleaseItemExclusive(context, id, lockId)

        ' Delete the session state file
        File.Delete(context.Server.MapPath(GetSessionFileName(id)))
    End Sub

    Public Overrides Function SetItemExpireCallback( _
        ByVal expireCallback As SessionStateItemExpireCallback _
        ) As Boolean
        ' This provider doesn’t support expiration callbacks,
        ' so simply return false here
        Return False
    End Function

    Public Overrides Sub InitializeRequest( _
        ByVal context As HttpContext)
    End Sub

    Public Overrides Sub EndRequest(ByVal context As HttpContext)
    End Sub

    Public Overrides Sub Dispose()
        ' Make sure no session state files are left open
        For Each pair As KeyValuePair(Of String, FileStream) _
            In _sessions
            pair.Value.Close()
            _sessions.Remove(pair.Key)
        Next

        ' Delete session files and lock files
        File.Delete( _
            HostingEnvironment.MapPath( _
            "~/App_Data/Session_Data/*_Session.txt"))
        File.Delete( _
            HostingEnvironment.MapPath( _
            "~/App_Data/Session_Data/*_Lock.txt"))
    End Sub

    ' Helper methods
    Private Function GetSession( _
        ByVal context As HttpContext, _
        ByVal id As String, ByRef locked As Boolean, _
        ByRef lockAge As TimeSpan, ByRef lockId As Object, _
        ByRef actions As SessionStateActions, _
        ByVal exclusive As Boolean) As SessionStateStoreData
        ' Assign default values to out parameters
        locked = False
        lockId = Nothing
        lockAge = TimeSpan.Zero
        actions = SessionStateActions.None

        Dim stream As FileStream = Nothing

        Try
            ' Attempt to open the session state file
            Dim path As String = context.Server.MapPath( _
                    GetSessionFileName(id))
            Dim access As FileAccess
            If exclusive Then
                access = FileAccess.ReadWrite
            Else
                access = FileAccess.Read
            End If
            Dim share As FileShare
            If exclusive Then
                share = FileShare.None
            Else
                share = FileShare.Read
            End If
            stream = File.Open(path, FileMode.Open, access, share)
        Catch e1 As FileNotFoundException
            ' Not an error if file doesn’t exist
            Return Nothing
        Catch e2 As IOException
            ' If we come here, the session is locked because
            ' the file couldn’t be opened
            locked = True
            lockId = id
            lockAge = GetLockAge(context, id)
            Return Nothing
        End Try

        ' Place a lock on the session
        CreateLock(context, id)
        locked = True
        lockId = id

        ' Save the FileStream reference so it can be used later
        _sessions.Add(id, stream)

        ' Find out whether the session is initialized
        Dim reader As StreamReader = New StreamReader(stream)
        Dim flag As String = reader.ReadLine()
        Dim initialized As Boolean = (flag = "1")

        If (Not initialized) Then
            ' Return an empty SessionStateStoreData
            actions = SessionStateActions.InitializeItem
            Dim timeout As Integer = _
                Convert.ToInt32(reader.ReadLine())

            Return New SessionStateStoreData( _
            New SessionStateItemCollection(), _
            SessionStateUtility.GetSessionStaticObjects(context), _
            timeout)
        Else
            ' Read Items, StaticObjects, and Timeout from the file
            ' (NOTE: Don’t close the StreamReader, because doing so
            ' will close the file)
            Dim items As Byte() = Convert.FromBase64String( _
                reader.ReadLine())
            Dim statics As Byte() = Convert.FromBase64String( _
                reader.ReadLine())
            Dim timeout As Integer = Convert.ToInt32( _
                reader.ReadLine())

            ' Deserialize the session
            Return DeserializeSession(items, statics, timeout)
        End If
    End Function

    Private Sub CreateLock(ByVal context As HttpContext, _
        ByVal id As String)
        ' Create a lock file so the lock’s age can be determined
        File.Create( _
            context.Server.MapPath(GetLockFileName(id))).Close()
    End Sub

    Private Sub ReleaseLock(ByVal context As HttpContext, _
        ByVal id As String)
        ' Delete the lock file
        Dim path As String = context.Server.MapPath( _
            GetLockFileName(id))
        If File.Exists(path) Then
            File.Delete(path)
        End If
    End Sub

    Private Function GetLockAge(ByVal context As HttpContext, _
        ByVal id As String) As TimeSpan
        Try
            Return New TimeSpan(File.GetCreationTime( _
                context.Server.MapPath(GetLockFileName(id))).Ticks)
        Catch e1 As FileNotFoundException
            ' This is important, because it’s possible that
            ' a lock is active but the lock file hasn’t been
            ' created yet if another thread owns the lock
            Return TimeSpan.Zero
        End Try
    End Function


    Private Function GetSessionFileName(ByVal id As String) As String
        Return String.Format( _
            "~/App_Data/Session_Data/{0}_Session.txt", id)
    End Function

    Private Function GetLockFileName(ByVal id As String) As String
        Return String.Format( _
            "~/App_Data/Session_Data/{0}_Lock.txt", id)
    End Function

    Private Sub SerializeSession( _
        ByVal store As SessionStateStoreData, _
        ByRef items As Byte(), _
        ByRef statics As Byte())
        Dim stream1 As MemoryStream = _
            Nothing, stream2 As MemoryStream = Nothing
        Dim writer1 As BinaryWriter = Nothing, _
            writer2 As BinaryWriter = Nothing

        Try
            stream1 = New MemoryStream()
            stream2 = New MemoryStream()
            writer1 = New BinaryWriter(stream1)
            writer2 = New BinaryWriter(stream2)

            CType(store.Items, _
                SessionStateItemCollection).Serialize(writer1)
            store.StaticObjects.Serialize(writer2)

            items = stream1.ToArray()
            statics = stream2.ToArray()
        Finally
            If Not writer2 Is Nothing Then
                writer2.Close()
            End If
            If Not writer1 Is Nothing Then
                writer1.Close()
            End If
            If Not stream2 Is Nothing Then
                stream2.Close()
            End If
            If Not stream1 Is Nothing Then
                stream1.Close()
            End If
        End Try
    End Sub

    Private Function DeserializeSession( _
        ByVal items As Byte(), ByVal statics As Byte(), _
        ByVal timeout As Integer) As SessionStateStoreData
        Dim stream1 As MemoryStream = Nothing, _
        stream2 As MemoryStream = Nothing
        Dim reader1 As BinaryReader = Nothing, _
        reader2 As BinaryReader = Nothing

        Try
            stream1 = New MemoryStream(items)
            stream2 = New MemoryStream(statics)
            reader1 = New BinaryReader(stream1)
            reader2 = New BinaryReader(stream2)

            Return New SessionStateStoreData( _
                SessionStateItemCollection.Deserialize(reader1), _
                HttpStaticObjectsCollection.Deserialize(reader2), _
                timeout)
        Finally
            If Not reader2 Is Nothing Then
                reader2.Close()
            End If
            If Not reader1 Is Nothing Then
                reader1.Close()
            End If
            If Not stream2 Is Nothing Then
                stream2.Close()
            End If
            If Not stream1 Is Nothing Then
                stream1.Close()
            End If
        End Try
    End Function
End Class

TextFileSessionStateProvider's locking strategy is built around file-sharing modes. GetItemExclusive attempts to open the session state file with a FileShare.None flag, giving it exclusive access to the file. GetItem, however, attempts to open the session state file with a FileShare.Read flag, allowing other readers to access the file, but not writers. So that GetItem and GetItemExclusive can return the lock's age if the session state file is locked when they're called, a 0-byte "lock file" named SessionID_Lock.txt is created in the ~/App_Data/Session_Data directory when a session state file is successfully opened. GetItem and GetItemExclusive compute a lock's age by subtracting the lock file's creation time from the current time. ReleaseItemExclusive and SetAndReleaseItemExclusive release a lock by closing the session state file and deleting the corresponding lock file.

TextFileSessionStateProvider takes a simple approach to lock IDs. When it creates a lock, it assigns the lock a lock ID that equals the session ID. That's sufficient because the nature of the locks managed by TextFileSessionStateProvider is such that a given session never has more than one lock applied to it. This behavior is consistent with that of SqlSessionStateStore, which also uses one lock ID per given session.

Listing 2 demonstrates how to make TextFileSessionStateProvider the default session state provider. It assumes that TextFileSessionStateProvider is implemented in an assembly named CustomProviders. Note the syntactical differences between session state providers and other provider types. The <sessionState> element uses a customProvider attribute rather than a defaultProvider attribute to designate the default provider, and the customProvider attribute is ignored unless a mode="Custom" attribute is included, too.

Listing 2. Web.config file making TextFileSessionStateProvider the default session state provider

<configuration>
  <system.web>
    <sessionState mode="Custom"
      customProvider="TextFileSessionStateProvider">
      <providers>
        <add name="TextFileSessionStateProvider"
          type="TextFileSessionStateProvider" />
      </providers>
    </sessionState>
  </system.web>
</configuration>

TextFileSessionStateProvider is fully capable of reading and writing any session state generated by application code. It does not, however, support expiration callbacks. In fact, the session state files that it generates don't get cleaned up until the provider's Dispose method is called, which normally occurs when the application is shut down and the AppDomain is unloaded.

Click here to continue on to part 5, Profile Providers.

© Microsoft Corporation. All rights reserved.