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:
- How does a session state provider know when to apply a lock?
- 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.