本文示范了一个会话状态存储提供者的实现。
如下代码范例程序说明了如何实现一个会话状态存储提供者。关于如何建立这个提供者并在 ASP.NET 应用程序中进行使用的详细信息,请参考:[ASP.NET 会话状态存储提供者的范例]。
using System; using System.Web; using System.Web.Configuration; using System.Configuration; using System.Configuration.Provider; using System.Collections.Specialized; using System.Web.SessionState; using System.Data; using System.Data.Odbc; using System.Diagnostics; using System.IO; /* 这是会话状态存储提供者所支持的数据库结构: CREATE TABLE Sessions ( SessionId Text(80) NOT NULL, ApplicationName Text(255) NOT NULL, Created DateTime NOT NULL, Expires DateTime NOT NULL, LockDate DateTime NOT NULL, LockId Integer NOT NULL, Timeout Integer NOT NULL, Locked YesNo NOT NULL, SessionItems Memo, Flags Integer NOT NULL, CONSTRAINT PKSessions PRIMARY KEY (SessionId, ApplicationName) ) 这个会话状态存储提供者并不自动清除已过期的会话项数据。建议你周期性地使用如下所示的代码从数据存储中删除已过期的会话信息(conn 是指会话状态存储提供者的 OdbcConnection): string commandString = "DELETE FROM Sessions WHERE Expires < ?"; OdbcConnection conn = new OdbcConnection(connectionString); OdbcCommand cmd = new OdbcCommand(commandString, conn); cmd.Parameters.Add("@Expires", OdbcType.DateTime).Value = DateTime.Now; conn.Open(); cmd.ExecuteNonQuery(); conn.Close(); */ namespace Samples.AspNet.Session { public sealed class OdbcSessionStateStore : SessionStateStoreProviderBase { private SessionStateSection pConfig = null; private string connectionString; private ConnectionStringSettings pConnectionStringSettings; private string eventSource = "OdbcSessionStateStore"; private string eventLog = "Application"; private string exceptionMessage = "An exception occurred. Please contact your administrator."; private string pApplicationName; // // 如果是 false,将抛出一个异常给调用者。 // 如果是 true,异常将被写入到事件日志中。 // private bool pWriteExceptionsToEventLog = false; public bool WriteExceptionsToEventLog { get { return pWriteExceptionsToEventLog; } set { pWriteExceptionsToEventLog = value; } } // // ApplicationName 属性被用来区分数据源中的会话。 // public string ApplicationName { get { return pApplicationName; } } public override void Initialize(string name, NameValueCollection config) { // // 从 Web.config 文件初始化值。 // if (config == null) throw new ArgumentNullException("config"); if (name == null || name.Length == 0) name = "OdbcSessionStateStore"; if (String.IsNullOrEmpty(config["description"])) { config.Remove("description"); config.Add("description", "Sample ODBC Session State Store provider"); } // 初始化抽象基类。 base.Initialize(name, config); // // 初始化 ApplicationName 属性。 // pApplicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath; // // 获取 <sessionState> 配置元素。 // Configuration cfg = WebConfigurationManager.OpenWebConfiguration(ApplicationName); pConfig = (SessionStateSection)cfg.GetSection("system.web/sessionState"); // // 初始化连接字符串。 // pConnectionStringSettings = ConfigurationManager.ConnectionStrings[config["connectionStringName"]]; if (pConnectionStringSettings == null || pConnectionStringSettings.ConnectionString.Trim() == "") { throw new ProviderException("Connection string cannot be blank."); } connectionString = pConnectionStringSettings.ConnectionString; // // 初始化 WriteExceptionsToEventLog // pWriteExceptionsToEventLog = false; if (config["writeExceptionsToEventLog"] != null) { if (config["writeExceptionsToEventLog"].ToUpper() == "TRUE") pWriteExceptionsToEventLog = true; } } // // SessionStateStoreProviderBase 的成员 // public override void Dispose() { } // // SessionStateProviderBase.SetItemExpireCallback // public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback) { return false; } // // SessionStateProviderBase.SetAndReleaseItemExclusive // public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem) { // 把 SessionStateItemCollection 序列化成一个字符串。 string sessItems = Serialize((SessionStateItemCollection)item.Items); OdbcConnection conn = new OdbcConnection(connectionString); OdbcCommand cmd; OdbcCommand deleteCmd = null; if (newItem) { // 用来清除现有的已过期会话的 OdbcCommand。 deleteCmd = new OdbcCommand("DELETE FROM Sessions " + "WHERE SessionId = ? AND ApplicationName = ? AND Expires < ?", conn); deleteCmd.Parameters.Add("@SessionId", OdbcType.VarChar, 80).Value = id; deleteCmd.Parameters.Add ("@ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName; deleteCmd.Parameters.Add ("@Expires", OdbcType.DateTime).Value = DateTime.Now; // 用来插入新会话项的 OdbcCommand。 cmd = new OdbcCommand("INSERT INTO Sessions " + " (SessionId, ApplicationName, Created, Expires, " + " LockDate, LockId, Timeout, Locked, SessionItems, Flags) " + " Values(?, ?, ?, ?, ?, ? , ?, ?, ?, ?)", conn); cmd.Parameters.Add("@SessionId", OdbcType.VarChar, 80).Value = id; cmd.Parameters.Add ("@ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName; cmd.Parameters.Add ("@Created", OdbcType.DateTime).Value = DateTime.Now; cmd.Parameters.Add ("@Expires", OdbcType.DateTime).Value = DateTime.Now.AddMinutes((Double)item.Timeout); cmd.Parameters.Add ("@LockDate", OdbcType.DateTime).Value = DateTime.Now; cmd.Parameters.Add("@LockId", OdbcType.Int).Value = 0; cmd.Parameters.Add ("@Timeout", OdbcType.Int).Value = item.Timeout; cmd.Parameters.Add("@Locked", OdbcType.Bit).Value = false; cmd.Parameters.Add ("@SessionItems", OdbcType.VarChar, sessItems.Length).Value = sessItems; cmd.Parameters.Add("@Flags", OdbcType.Int).Value = 0; } else { // 用来更新现有会话项的 OdbcCommand。 cmd = new OdbcCommand( "UPDATE Sessions SET Expires = ?, SessionItems = ?, Locked = ? " + " WHERE SessionId = ? AND ApplicationName = ? AND LockId = ?", conn); cmd.Parameters.Add("@Expires", OdbcType.DateTime).Value = DateTime.Now.AddMinutes((Double)item.Timeout); cmd.Parameters.Add("@SessionItems", OdbcType.VarChar, sessItems.Length).Value = sessItems; cmd.Parameters.Add("@Locked", OdbcType.Bit).Value = false; cmd.Parameters.Add("@SessionId", OdbcType.VarChar, 80).Value = id; cmd.Parameters.Add("@ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName; cmd.Parameters.Add("@LockId", OdbcType.Int).Value = lockId; } try { conn.Open(); if (deleteCmd != null) deleteCmd.ExecuteNonQuery(); cmd.ExecuteNonQuery(); } catch (OdbcException e) { if (WriteExceptionsToEventLog) { WriteToEventLog(e, "SetAndReleaseItemExclusive"); throw new ProviderException(exceptionMessage); } else throw e; } finally { conn.Close(); } } // // SessionStateProviderBase.GetItem // public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags) { return GetSessionStoreItem(false, context, id, out locked, out lockAge, out lockId, out actionFlags); } // // SessionStateProviderBase.GetItemExclusive // public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags) { return GetSessionStoreItem(true, context, id, out locked, out lockAge, out lockId, out actionFlags); } // // GetSessionStoreItem 在 GetItem 或 GetItemExclusive 方法被调用的时候会被调用。GetSessionStoreItem 从数据源中获取会话数据。如果 lockRecord 参数被设置成 true(在 GetItemExclusive 方法被调用的情况下),那么 GetSessionStoreItem 会锁定记录并设置一个新的 LockId 和 LockDate。 // private SessionStateStoreData GetSessionStoreItem(bool lockRecord, HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags) { // 初始化返回值和输出参数的值。 SessionStateStoreData item = null; lockAge = TimeSpan.Zero; lockId = null; locked = false; actionFlags = 0; // ODBC 数据库连接。 OdbcConnection conn = new OdbcConnection(connectionString); // 用于数据库命令的 OdbcCommand。 OdbcCommand cmd = null; // 用来读取数据库记录的 DataReader。 OdbcDataReader reader = null; // 检查当前会话项是否过期的 DateTime。 DateTime expires; // 保持 SessionStateItemCollection 的序列化字符串。 string serializedItems = ""; // 在数据库中找到记录的时候值为 True。 bool foundRecord = false; // 在被返回的会话项已经过期并且需要被删除的时候值为 True。 bool deleteData = false; // 来自于数据存储的超时限制。 int timeout = 0; try { conn.Open(); // 在 GetItemExclusive 被调用的时候 lockRecord 的值为 true,而在 GetItem 被调用的时候 lockRecord 的值为 false。 // 如果可能就获得一个锁定。并在记录已过期的时候被忽略。 if (lockRecord) { cmd = new OdbcCommand( "UPDATE Sessions SET" + " Locked = ?, LockDate = ? " + " WHERE SessionId = ? AND ApplicationName = ? AND Locked = ? AND Expires > ?", conn); cmd.Parameters.Add("@Locked", OdbcType.Bit).Value = true; cmd.Parameters.Add("@LockDate", OdbcType.DateTime).Value = DateTime.Now; cmd.Parameters.Add("@SessionId", OdbcType.VarChar, 80).Value = id; cmd.Parameters.Add("@ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName; cmd.Parameters.Add("@Locked", OdbcType.Int).Value = false; cmd.Parameters.Add ("@Expires", OdbcType.DateTime).Value = DateTime.Now; if (cmd.ExecuteNonQuery() == 0) // 因为记录被锁定或者没有找到,所以没有记录被更新。 locked = true; else // 记录已经被更新。 locked = false; } // 获取当前的会话项信息。 cmd = new OdbcCommand( "SELECT Expires, SessionItems, LockId, LockDate, Flags, Timeout " + " FROM Sessions " + " WHERE SessionId = ? AND ApplicationName = ?", conn); cmd.Parameters.Add("@SessionId", OdbcType.VarChar, 80).Value = id; cmd.Parameters.Add("@ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName; // 从数据源中获取会话项数据。 reader = cmd.ExecuteReader(CommandBehavior.SingleRow); while (reader.Read()) { expires = reader.GetDateTime(0); if (expires < DateTime.Now) { // 记录已经过期。把它标记成已锁定的。 locked = false; // 会话已经过期。把它标记成要删除的。 deleteData = true; } else foundRecord = true; serializedItems = reader.GetString(1); lockId = reader.GetInt32(2); lockAge = DateTime.Now.Subtract(reader.GetDateTime(3)); actionFlags = (SessionStateActions)reader.GetInt32(4); timeout = reader.GetInt32(5); } reader.Close(); // 如果被返回的会话项已经过期, // 就从数据源中删除该记录。 if (deleteData) { cmd = new OdbcCommand("DELETE FROM Sessions " + "WHERE SessionId = ? AND ApplicationName = ?", conn); cmd.Parameters.Add("@SessionId", OdbcType.VarChar, 80).Value = id; cmd.Parameters.Add("@ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName; cmd.ExecuteNonQuery(); } // 记录未找到。确保 locked 的值为 false。 if (!foundRecord) locked = false; // 如果记录已找到并且你已经获得了一个锁定,那么设置 lockId 的值,并清除 actionFlags,然后创建一个 SessionStateStoreItem 来作为要返回的对象。 if (foundRecord && !locked) { lockId = (int)lockId + 1; cmd = new OdbcCommand("UPDATE Sessions SET" + " LockId = ?, Flags = 0 " + " WHERE SessionId = ? AND ApplicationName = ?", conn); cmd.Parameters.Add("@LockId", OdbcType.Int).Value = lockId; cmd.Parameters.Add("@SessionId", OdbcType.VarChar, 80).Value = id; cmd.Parameters.Add("@ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName; cmd.ExecuteNonQuery(); // 如果 actionFlags 参数的值不是 InitializeItem, // 就解序列化被存储的 SessionStateItemCollection。 if (actionFlags == SessionStateActions.InitializeItem) item = CreateNewStoreData(context, pConfig.Timeout.Minutes); else item = Deserialize(context, serializedItems, timeout); } } catch (OdbcException e) { if (WriteExceptionsToEventLog) { WriteToEventLog(e, "GetSessionStoreItem"); throw new ProviderException(exceptionMessage); } else throw e; } finally { if (reader != null) { reader.Close(); } conn.Close(); } return item; } // // 序列化通过 SetAndReleaseItemExclusive 方法被调用来把 SessionStateItemCollection 转换成一个基于 64 位的字符串并存储到 Access 的 Memo 类型的字段中。 // private string Serialize(SessionStateItemCollection items) { MemoryStream ms = new MemoryStream(); BinaryWriter writer = new BinaryWriter(ms); if (items != null) items.Serialize(writer); writer.Close(); return Convert.ToBase64String(ms.ToArray()); } // // DeSerialize 通过 GetSessionStoreItem 方法被调用来把被存储在 Access 的 Memo 类型的字段中基于 64 位的字符串转换成一个 SessionStateItemCollection。 // private SessionStateStoreData Deserialize(HttpContext context, string serializedItems, int timeout) { MemoryStream ms = new MemoryStream(Convert.FromBase64String(serializedItems)); BinaryReader reader = new BinaryReader(ms); SessionStateItemCollection sessionItems = SessionStateItemCollection.Deserialize(reader); return new SessionStateStoreData(sessionItems, SessionStateUtility.GetSessionStaticObjects(context), timeout); } // // SessionStateProviderBase.ReleaseItemExclusive // public override void ReleaseItemExclusive(HttpContext context, string id, object lockId) { OdbcConnection conn = new OdbcConnection(connectionString); OdbcCommand cmd = new OdbcCommand("UPDATE Sessions SET Locked = 0, Expires = ? " + "WHERE SessionId = ? AND ApplicationName = ? AND LockId = ?", conn); cmd.Parameters.Add("@Expires", OdbcType.DateTime).Value = DateTime.Now.AddMinutes(pConfig.Timeout.Minutes); cmd.Parameters.Add("@SessionId", OdbcType.VarChar, 80).Value = id; cmd.Parameters.Add("@ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName; cmd.Parameters.Add("@LockId", OdbcType.Int).Value = lockId; try { conn.Open(); cmd.ExecuteNonQuery(); } catch (OdbcException e) { if (WriteExceptionsToEventLog) { WriteToEventLog(e, "ReleaseItemExclusive"); throw new ProviderException(exceptionMessage); } else throw e; } finally { conn.Close(); } } // // SessionStateProviderBase.RemoveItem // public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item) { OdbcConnection conn = new OdbcConnection(connectionString); OdbcCommand cmd = new OdbcCommand("DELETE * FROM Sessions " + "WHERE SessionId = ? AND ApplicationName = ? AND LockId = ?", conn); cmd.Parameters.Add("@SessionId", OdbcType.VarChar, 80).Value = id; cmd.Parameters.Add("@ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName; cmd.Parameters.Add("@LockId", OdbcType.Int).Value = lockId; try { conn.Open(); cmd.ExecuteNonQuery(); } catch (OdbcException e) { if (WriteExceptionsToEventLog) { WriteToEventLog(e, "RemoveItem"); throw new ProviderException(exceptionMessage); } else throw e; } finally { conn.Close(); } } // // SessionStateProviderBase.CreateUninitializedItem // public override void CreateUninitializedItem(HttpContext context, string id, int timeout) { OdbcConnection conn = new OdbcConnection(connectionString); OdbcCommand cmd = new OdbcCommand("INSERT INTO Sessions " + " (SessionId, ApplicationName, Created, Expires, " + " LockDate, LockId, Timeout, Locked, SessionItems, Flags) " + " Values(?, ?, ?, ?, ?, ? , ?, ?, ?, ?)", conn); cmd.Parameters.Add("@SessionId", OdbcType.VarChar, 80).Value = id; cmd.Parameters.Add("@ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName; cmd.Parameters.Add("@Created", OdbcType.DateTime).Value = DateTime.Now; cmd.Parameters.Add("@Expires", OdbcType.DateTime).Value = DateTime.Now.AddMinutes((Double)timeout); cmd.Parameters.Add("@LockDate", OdbcType.DateTime).Value = DateTime.Now; cmd.Parameters.Add("@LockId", OdbcType.Int).Value = 0; cmd.Parameters.Add("@Timeout", OdbcType.Int).Value = timeout; cmd.Parameters.Add("@Locked", OdbcType.Bit).Value = false; cmd.Parameters.Add("@SessionItems", OdbcType.VarChar, 0).Value = ""; cmd.Parameters.Add("@Flags", OdbcType.Int).Value = 1; try { conn.Open(); cmd.ExecuteNonQuery(); } catch (OdbcException e) { if (WriteExceptionsToEventLog) { WriteToEventLog(e, "CreateUninitializedItem"); throw new ProviderException(exceptionMessage); } else throw e; } finally { conn.Close(); } } // // SessionStateProviderBase.CreateNewStoreData // public override SessionStateStoreData CreateNewStoreData( HttpContext context, int timeout) { return new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), timeout); } // // SessionStateProviderBase.ResetItemTimeout // public override void ResetItemTimeout(HttpContext context, string id) { OdbcConnection conn = new OdbcConnection(connectionString); OdbcCommand cmd = new OdbcCommand("UPDATE Sessions SET Expires = ? " + "WHERE SessionId = ? AND ApplicationName = ?", conn); cmd.Parameters.Add("@Expires", OdbcType.DateTime).Value = DateTime.Now.AddMinutes(pConfig.Timeout.Minutes); cmd.Parameters.Add("@SessionId", OdbcType.VarChar, 80).Value = id; cmd.Parameters.Add("@ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName; try { conn.Open(); cmd.ExecuteNonQuery(); } catch (OdbcException e) { if (WriteExceptionsToEventLog) { WriteToEventLog(e, "ResetItemTimeout"); throw new ProviderException(exceptionMessage); } else throw e; } finally { conn.Close(); } } // // SessionStateProviderBase.InitializeRequest // public override void InitializeRequest(HttpContext context) { } // // SessionStateProviderBase.EndRequest // public override void EndRequest(HttpContext context) { } // // WriteToEventLog 方法 // 这是一个把异常的详细内容写入到事件日志中的助手方法。 // 异常是当作一个确保私有数据库的细节不被返回给浏览器的安全评估而被写入到事件日志中的。 // 即使某个方法没有返回用来表示动作是否完成或者是否失败的状态或布尔值,调用者还是同样能够抛出一个常规的异常。 // private void WriteToEventLog(Exception e, string action) { EventLog log = new EventLog(); log.Source = eventSource; log.Log = eventLog; string message = "An exception occurred communicating with the data source.\n\n"; message += "Action: " + action + "\n\n"; message += "Exception: " + e.ToString(); log.WriteEntry(message); } } }