using System;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.SessionState;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Configuration.Provider;
using System.Security.Permissions;
using System.Web.Hosting;
using System.Diagnostics;
using System.IO;
namespace ThreeOaks.TextFileSession
{
public class TextFileSessionStateStore :
SessionStateStoreProviderBase, ThreeOaks.SessionProviders.IThreeOaksSessionProvider
{
private Dictionary<string, FileStream> _sessions = new Dictionary<string, FileStream>();
/// <summary>
/// Holds the location of the files
/// </summary>
private string fileLocation = String.Empty;
public override void Initialize(string name, NameValueCollection config)
{
Debug.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name);
// Verify that config isn't null
if (config == null)
throw new ArgumentNullException("config");
// Assign the provider a default name if it doesn't have one
if (String.IsNullOrEmpty(name))
name = "TextFileSessionStateProvider";
// Add a default "description" attribute to config if the
// attribute doesn't exist or is empty
if (string.IsNullOrEmpty(config["description"]))
{
config.Remove("description");
config.Add("description",
"Text file session state provider");
}
//Don't have an entry for where files are, so default to folder
//'Session_Data' in 'App_Data'
if (string.IsNullOrEmpty(config["fileLocation"]))
{
config.Remove("fileLocation");
config.Add("fileLocation", HttpContext.Current.Server.MapPath("~/App_Data/Session_Data"));
fileLocation = HttpContext.Current.Server.MapPath("~/App_Data/Session_Data");
}
else
{
fileLocation = config["fileLocation"].ToString();
}
// Call the base class's Initialize method
base.Initialize(name, config);
// Throw an exception if unrecognized attributes remain
if (config.Count > 1)
{
string attr = config.GetKey(1);
if (!String.IsNullOrEmpty(attr))
throw new ProviderException
("Unrecognized attribute: " + attr);
}
// Make sure we can read and write files in the
// ~/App_Data/Session_Data directory
FileIOPermission permission =
new FileIOPermission(FileIOPermissionAccess.AllAccess,
fileLocation);
permission.Demand();
}
public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
{
Debug.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name);
return new SessionStateStoreData(
new SessionStateItemCollection(),
SessionStateUtility.GetSessionStaticObjects(context),
timeout
);
}
public override void CreateUninitializedItem(HttpContext context,
string id, int timeout)
{
Debug.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name);
// Create a file containing an uninitialized flag
// and a time-out
StreamWriter writer = null;
try
{
writer = new StreamWriter(
(GetSessionFileName(id)));
writer.WriteLine("0");
writer.WriteLine(timeout.ToString());
}
finally
{
if (writer != null)
writer.Close();
}
}
public override SessionStateStoreData GetItem(HttpContext context,
string id, out bool locked, out TimeSpan lockAge,
out object lockId, out SessionStateActions actions)
{
Debug.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name);
return GetSession(context, id, out locked, out lockAge,
out lockId, out actions, false);
}
public override SessionStateStoreData GetItemExclusive(HttpContext context, string id,
out bool locked, out TimeSpan lockAge, out object lockId,
out SessionStateActions actions)
{
Debug.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name);
return GetSession(context, id, out locked, out lockAge,
out lockId, out actions, true);
}
public override void SetAndReleaseItemExclusive(HttpContext context, string id,
SessionStateStoreData item, object lockId, bool newItem)
{
Debug.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name);
// Serialize the session
byte[] items, statics;
SerializeSession(item, out items, out statics);
string serializedItems = Convert.ToBase64String(items);
string serializedStatics = Convert.ToBase64String(statics);
// Get a FileStream representing the session state file
FileStream stream = null;
try
{
if (newItem)
stream = File.Create(
(GetSessionFileName(id)));
else
{
stream = _sessions[id];
stream.SetLength(0);
stream.Seek(0, SeekOrigin.Begin);
}
// Write session state to the file
StreamWriter writer = null;
try
{
writer = new StreamWriter(stream);
writer.WriteLine("1"); // Initialized flag
writer.WriteLine(serializedItems);
writer.WriteLine(serializedStatics);
writer.WriteLine(item.Timeout.ToString());
}
finally
{
if (writer != null)
writer.Close();
}
}
finally
{
if (newItem && stream != null)
stream.Close();
}
// Unlock the session
ReleaseItemExclusive(context, id, lockId);
}
public override void ReleaseItemExclusive(HttpContext context,
string id, object lockId)
{
Debug.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name);
// Release the specified session by closing the corresponding
// FileStream and deleting the lock file
FileStream stream;
if (_sessions.TryGetValue(id, out stream))
{
_sessions.Remove(id);
ReleaseLock(context, (string)lockId);
stream.Close();
}
}
/// <summary>
/// Changes the file creation date to now, resetting the expiration.
/// </summary>
/// <param name="context"></param>
/// <param name="id"></param>
public override void ResetItemTimeout(HttpContext context,
string id)
{
Debug.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name);
// Update the time stamp on the session state file
string path = GetSessionFileName(id);
File.SetCreationTime(path, DateTime.Now);
}
/// <summary>
/// Removes and locks on the files and deletes the session files.
/// </summary>
/// <param name="context"></param>
/// <param name="id"></param>
/// <param name="lockId"></param>
/// <param name="item"></param>
public override void RemoveItem(HttpContext context, string id,
object lockId, SessionStateStoreData item)
{
Debug.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name);
// Make sure the session is unlocked
ReleaseItemExclusive(context, id, lockId);
// Delete the session state file
File.Delete(GetSessionFileName(id));
}
/// <summary>
/// Tells asp.net if support expiration callback.
/// In this case, it doesn't.
/// </summary>
/// <param name="expireCallback"></param>
/// <returns></returns>
public override bool SetItemExpireCallback
(SessionStateItemExpireCallback expireCallback)
{
// This provider doesn't support expiration callbacks,
// so simply return false here
return false;
}
public override void InitializeRequest(HttpContext context)
{
}
public override void EndRequest(HttpContext context)
{
}
/// <summary>
/// Removes any locks on files and deletes all files.
/// </summary>
public override void Dispose()
{
Debug.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name);
// Make sure no session state files are left open
foreach (KeyValuePair<string, FileStream> pair in _sessions)
{
pair.Value.Close();
_sessions.Remove(pair.Key);
}
//Removed the following since removes all files, including those
//another server may be using. Using DeleteExpiredSessions instead
//in application end
// Delete session files and lock files
//Debug.WriteLine("Deleting session record.","Dispose");
//File.Delete(fileLocation + "\\*_Session.txt");
//File.Delete(fileLocation + "\\*_Lock.txt");
}
// Helper methods
private SessionStateStoreData GetSession(HttpContext context,
string id, out bool locked, out TimeSpan lockAge,
out object lockId, out SessionStateActions actions,
bool exclusive)
{
Debug.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name);
// Assign default values to out parameters
SessionStateStoreData sessionStateStoreData = null;
locked = false;
lockId = null;
lockAge = TimeSpan.Zero;
actions = SessionStateActions.None;
FileStream stream = null;
try
{
// Attempt to open the session state file
string path = GetSessionFileName(id);
FileAccess access = exclusive ? FileAccess.ReadWrite : FileAccess.Read;
FileShare share = exclusive ? FileShare.None : FileShare.Read;
stream = File.Open(path, FileMode.Open, access, share);
}
catch (FileNotFoundException)
{
// Not an error if file doesn't exist
return null;
}
catch (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 null;
}
// Place a lock on the session
CreateLock(context, id);
locked = true;
lockId = id; //TODO: If lockId is id, may be a problem with pages making multiple calls at once like pages with IFRAME.
// Save the FileStream reference so it can be used later
_sessions.Add(id, stream);
// Find out whether the session is initialized
StreamReader reader = new StreamReader(stream);
string flag = reader.ReadLine();
bool initialized = (flag == "1");
if (!initialized)
{
// Return an empty SessionStateStoreData
actions = SessionStateActions.InitializeItem;
int timeout = Convert.ToInt32(reader.ReadLine());
sessionStateStoreData = 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)
byte[] items = Convert.FromBase64String(reader.ReadLine());
byte[] statics = Convert.FromBase64String(reader.ReadLine());
int timeout = Convert.ToInt32(reader.ReadLine());
// Deserialize the session
sessionStateStoreData = DeserializeSession(items, statics, timeout);
}
//Want locked while reading, but when done remove lock.
//Added so that pages that are read-only on multiple requests work.
//Original code would add a value to _session, but not remove it
//and the lock.
if (exclusive == false)
{
ReleaseItemExclusive(context, id, lockId);
}
return sessionStateStoreData;
}
/// <summary>
/// Creates a lock file.
/// </summary>
/// <param name="context"></param>
/// <param name="id"></param>
private void CreateLock(HttpContext context, string id)
{
// Create a lock file so the lock's age can be determined
File.Create(GetLockFileName(id)).Close();
}
/// <summary>
/// Deletes the lock file.
/// </summary>
/// <param name="context"></param>
/// <param name="id"></param>
private void ReleaseLock(HttpContext context, string id)
{
// Delete the lock file
string path = GetLockFileName(id);
if (File.Exists(path))
File.Delete(path);
}
private TimeSpan GetLockAge(HttpContext context, string id)
{
try
{
return DateTime.Now -
File.GetCreationTime(GetLockFileName(id));
}
catch (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;
}
}
string GetSessionFileName(string id)
{
return String.Format(fileLocation + "\\{0}_Session.txt", id);
}
string GetLockFileName(string id)
{
return String.Format(fileLocation + "\\{0}_Lock.txt", id);
}
private void SerializeSession(SessionStateStoreData store,
out byte[] items, out byte[] statics)
{
MemoryStream stream1 = null, stream2 = null;
BinaryWriter writer1 = null, writer2 = null;
try
{
stream1 = new MemoryStream();
stream2 = new MemoryStream();
writer1 = new BinaryWriter(stream1);
writer2 = new BinaryWriter(stream2);
((SessionStateItemCollection)
store.Items).Serialize(writer1);
store.StaticObjects.Serialize(writer2);
items = stream1.ToArray();
statics = stream2.ToArray();
}
finally
{
if (writer2 != null)
writer2.Close();
if (writer1 != null)
writer1.Close();
if (stream2 != null)
stream2.Close();
if (stream1 != null)
stream1.Close();
}
}
private SessionStateStoreData DeserializeSession(byte[] items,
byte[] statics, int timeout)
{
MemoryStream stream1 = null, stream2 = null;
BinaryReader reader1 = null, reader2 = null;
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 (reader2 != null)
reader2.Close();
if (reader1 != null)
reader1.Close();
if (stream2 != null)
stream2.Close();
if (stream1 != null)
stream1.Close();
}
}
/// <summary>
/// Deletes any expired Sessions
/// </summary>
public void DeleteExpiredSessions()
{
// Delete session files and lock files
System.Diagnostics.Debug.WriteLine("Deleting session record.", "Dispose");
FileStream stream = null;
using (StreamReader reader = new StreamReader(stream))
{
System.IO.DirectoryInfo dir = new DirectoryInfo(fileLocation);
foreach (FileSystemInfo info in dir.GetFileSystemInfos("*_Session.txt"))
{
//if have an error, keep going.
//Most likely error caused by a handle still on the file.
try
{
//need to find out if timed out
stream = File.Open(info.FullName, FileMode.Open, FileAccess.Read, FileShare.Delete);
string flag = reader.ReadLine();
int timeout = Convert.ToInt32(reader.ReadLine());
reader.Close();
stream.Close();
if (DateTime.Now > (info.CreationTime + new TimeSpan(0, timeout, 0)))
{
//delete the file
//get the id
string id = info.Name.Replace("_Session.txt", "");
//Files may still have a handle on it from another machine.
//So keep removing if have an error happens.
File.Delete(info.FullName);
File.Delete(fileLocation + "\\" + id + "_Lock.txt");
}
}
catch (Exception exception) { };
}
}
//File.Delete(HostingEnvironment.MapPath
//("~/App_Data/Session_Data/*_Session.txt"));
//File.Delete(HostingEnvironment.MapPath
//("~/App_Data/Session_Data/*_Lock.txt"));
}
}
}