Session是互聯網應用中非常重要的玩意兒,對於超過單台部署的站點集群,都會存在會話共享的需求。在web.config中,微軟提供了sessionstate節點來定義不同的Session狀態存儲方式。本文就自定義模式下的Session狀態存儲驅動提供一些干貨。
首先,想要接管Session狀態存儲,需要了解一些基本的東西。
SessionIDManager
/// <summary> /// 自定義SessionID管理器 /// </summary> public class CustomSessionIDManager : SessionIDManager { /// <summary> /// 創建SessionID /// </summary> /// <param name="context"></param> /// <returns></returns> public override string CreateSessionID(HttpContext context) { return string.Format("{0}.{1}", SessionProviderConfigurationSectionHandler.SessionProviderSettings.SessionProfix, base.CreateSessionID(context)); } /// <summary> /// 驗證 /// </summary> /// <param name="id"></param> /// <returns></returns> public override bool Validate(string id) { string prefix = string.Empty; string realId = string.Empty; if (string.IsNullOrEmpty(id) || id.Trim().Length == 0) { return false; } var parsedValues = id.Split('.'); if (parsedValues == null || parsedValues.Length != 2) { return false; } prefix = parsedValues[0]; realId = parsedValues[1]; return base.Validate(realId); } }
想要共享Session,肯定就會有SessionID的前綴需求,而SessionIDManager就提供了可自定義的虛方法,這邊以CustomSessionIDManager舉例。CreateSessionID方法提供了創建SessionID的實現,重載該方法,用{0}.{1}的方式提供基於前綴的SessionID生成。而Validate是拆解帶前綴的SessionID來驗證真實的SessionID。
SessionStateStoreProviderBase
/// <summary> /// 自定義Session狀態存儲驅動 /// </summary> public sealed class CustomSessionStateStoreProvider : SessionStateStoreProviderBase { /// <summary> /// 構造函數 /// </summary> public CustomSessionStateStoreProvider() { sessionStateStoreBehavior = SessionProviderBehaviorFactory.CreateSessionStateStoreBehaviorInstance(); } /// <summary> /// Session狀態存儲行為 /// </summary> readonly ISessionStateStoreBehavior sessionStateStoreBehavior; /// <summary> /// 創建新的存儲數據 /// </summary> /// <param name="context"></param> /// <param name="timeout"></param> /// <returns></returns> public override SessionStateStoreData CreateNewStoreData(System.Web.HttpContext context, int timeout) { return new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), timeout); } /// <summary> /// 創建未初始化的項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="timeout"></param> public override void CreateUninitializedItem(System.Web.HttpContext context, string id, int timeout) { sessionStateStoreBehavior.CreateUninitializedItem(context, id, timeout); } /// <summary> /// 獲取項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="locked"></param> /// <param name="lockAge"></param> /// <param name="lockId"></param> /// <param name="actions"></param> /// <returns></returns> public override SessionStateStoreData GetItem(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions) { return sessionStateStoreBehavior.GetItem(false, context, id, out locked, out lockAge, out lockId, out actions); } /// <summary> /// 獨占獲取項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="locked"></param> /// <param name="lockAge"></param> /// <param name="lockId"></param> /// <param name="actions"></param> /// <returns></returns> public override SessionStateStoreData GetItemExclusive(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions) { return sessionStateStoreBehavior.GetItem(true, context, id, out locked, out lockAge, out lockId, out actions); } /// <summary> /// 獨占釋放項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="lockId"></param> public override void ReleaseItemExclusive(System.Web.HttpContext context, string id, object lockId) { sessionStateStoreBehavior.ReleaseItem(context, id, lockId); } /// <summary> /// 移除項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="lockId"></param> /// <param name="item"></param> public override void RemoveItem(System.Web.HttpContext context, string id, object lockId, SessionStateStoreData item) { sessionStateStoreBehavior.RemoveItem(context, id, lockId); } /// <summary> /// 重設項的超時時間 /// </summary> /// <param name="context"></param> /// <param name="id"></param> public override void ResetItemTimeout(System.Web.HttpContext context, string id) { sessionStateStoreBehavior.ResetItemTimeout(context, id); } /// <summary> /// 獨占設置並釋放項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="item"></param> /// <param name="lockId"></param> /// <param name="newItem"></param> public override void SetAndReleaseItemExclusive(System.Web.HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem) { sessionStateStoreBehavior.SetAndReleaseItem(context, id, item, lockId, newItem); } /// <summary> /// 回收 /// </summary> public override void Dispose() { } /// <summary> /// 結束請求 /// </summary> /// <param name="context"></param> public override void EndRequest(System.Web.HttpContext context) { } /// <summary> /// 初始化請求 /// </summary> /// <param name="context"></param> public override void InitializeRequest(System.Web.HttpContext context) { } /// <summary> /// 設置項過期回掉 /// </summary> /// <param name="expireCallback"></param> /// <returns></returns> public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback) { return false; } }
SessionStateStoreProviderBase是提供Session狀態存儲驅動的基類。從基類分析,想要靈活擴展,核心就是對Session存儲的那些方法實現進行抽象,讓驅動在執行方法的時候不關心實現由誰來提供。因此,寫一個SessionStateStoreBehavior接口,在CustomSessionStateStoreProvider的構造函數中,通過工廠在運行時得到實例。
SessionStateStoreBehavior
/// <summary> /// Session狀態存儲行為接口 /// </summary> public interface ISessionStateStoreBehavior { /// <summary> /// 創建未初始化的項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="timeout"></param> void CreateUninitializedItem(System.Web.HttpContext context, string id, int timeout); /// <summary> /// 獲取項 /// </summary> /// <param name="lockRecord"></param> /// <param name="context"></param> /// <param name="id"></param> /// <param name="locked"></param> /// <param name="lockAge"></param> /// <param name="lockId"></param> /// <param name="actions"></param> /// <returns></returns> SessionStateStoreData GetItem(bool lockRecord, System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions); /// <summary> /// 釋放項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="lockId"></param> void ReleaseItem(System.Web.HttpContext context, string id, object lockId); /// <summary> /// 移除項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="lockId"></param> void RemoveItem(System.Web.HttpContext context, string id, object lockId); /// <summary> /// 重設項的超時時間 /// </summary> /// <param name="context"></param> /// <param name="id"></param> void ResetItemTimeout(System.Web.HttpContext context, string id); /// <summary> /// 設置並釋放項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="item"></param> /// <param name="lockId"></param> /// <param name="newItem"></param> void SetAndReleaseItem(System.Web.HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem); }
創建一個ISessionStateStoreBehavior,將涉及Session存儲的方法公開。
/// <summary> /// Session驅動行為工廠 /// </summary> class SessionProviderBehaviorFactory { /// <summary> /// 當前Session狀態存儲行為實例 /// </summary> static ISessionStateStoreBehavior currentSessionStateStoreBehavior; /// <summary> /// 創建Session狀態存儲行為實例 /// </summary> /// <returns></returns> public static ISessionStateStoreBehavior CreateSessionStateStoreBehaviorInstance() { if (currentSessionStateStoreBehavior == null) { var types = Assembly.GetExecutingAssembly().GetTypes().Where(t => !t.IsAbstract && t.GetInterface(typeof(ISessionStateStoreBehavior).Name) != null); var currentType = types.FirstOrDefault(t => t.Name == string.Format("{0}SessionStateStoreBehavior", SessionProviderConfigurationSectionHandler.SessionProviderSettings.SessionStateStoreProviderBehaviorName)); if (currentType != null) { currentSessionStateStoreBehavior = (ISessionStateStoreBehavior)Activator.CreateInstance(currentType); } } return currentSessionStateStoreBehavior; } }
通過工廠得到當前應用程序域下的繼承了ISessionStateStoreBehavior接口的所有實現類,並讀取配置得到當前實例。
基於MongoDB的一個行為實現
/// <summary> /// 基於Mongo的Session狀態存儲行為 /// </summary> public class MongoSessionStateStoreBehavior : SessionStateStoreBehaviorBase, ISessionStateStoreBehavior { /// <summary> /// 構造函數 /// </summary> public MongoSessionStateStoreBehavior() : base() { if (collection == null) { collection = GetMongoDBCollection(); var expiresKey = IndexKeys.Ascending("Expires"); var options = IndexOptions.SetTimeToLive(base.SessionStateSection.Timeout); collection.EnsureIndex(expiresKey, options); collection.EnsureIndex("LockId", "Locked"); } } static MongoCollection<MongoDBSessionDo> collection; /// <summary> /// 獲取Mongo集合 /// </summary> /// <returns></returns> static MongoCollection<MongoDBSessionDo> GetMongoDBCollection() { var url = new MongoUrl(SessionProviderConfigurationSectionHandler.SessionProviderSettings.StorageConnectionString); var client = new MongoClient(url); var database = client.GetServer().GetDatabase(url.DatabaseName, WriteConcern.Unacknowledged); return database.GetCollection<MongoDBSessionDo>(ConfigSection.SessionProviderConfigurationSectionHandler.SessionProviderSettings.SessionProfix); } /// <summary> /// 創建未初始化項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="timeout"></param> public void CreateUninitializedItem(System.Web.HttpContext context, string id, int timeout) { var session = new MongoDBSessionDo() { SessionId = id, Created = DateTime.Now, Expires = DateTime.Now.AddMinutes(base.SessionStateSection.Timeout.TotalMinutes), LockDate = DateTime.Now, LockId = 0, Timeout = timeout, Locked = false, Flags = (int)SessionStateActions.InitializeItem, }; collection.Save(session); SetSessionIdCookieExpires(context, session.Expires); } /// <summary> /// 獲取項 /// </summary> /// <param name="lockRecord"></param> /// <param name="context"></param> /// <param name="id"></param> /// <param name="locked"></param> /// <param name="lockAge"></param> /// <param name="lockId"></param> /// <param name="actions"></param> /// <returns></returns> public System.Web.SessionState.SessionStateStoreData GetItem(bool lockRecord, System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out System.Web.SessionState.SessionStateActions actions) { var item = default(SessionStateStoreData); lockAge = TimeSpan.Zero; lockId = null; locked = false; actions = 0; bool foundRecord = false; bool deleteData = false; var session = collection.AsQueryable().FirstOrDefault(s => s.SessionId == id); if (lockRecord) { locked = session != null && !session.Locked && session.Expires > DateTime.Now; } if (session != null) { if (session.Expires < DateTime.Now) { locked = false; deleteData = true; } else { foundRecord = true; } } if (deleteData) { collection.Remove(Query.EQ("_id", id)); } if (!foundRecord) locked = false; if (foundRecord && !locked) { lockId = lockId == null ? 0 : (int)lockId + 1; collection.Update(Query.EQ("_id", id), Update.Set("LockId", (int)lockId).Set("Flags", (int)SessionStateActions.None)); var timeout = actions == SessionStateActions.InitializeItem ? (int)base.SessionStateSection.Timeout.TotalMinutes : session.Timeout; var sessionStateItemCollection = actions == SessionStateActions.InitializeItem ? new SessionStateItemCollection() : Helper.Deserialize(session.SessionItems); item = new SessionStateStoreData(sessionStateItemCollection, SessionStateUtility.GetSessionStaticObjects(context), timeout); } return item; } /// <summary> /// 釋放項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="lockId"></param> public void ReleaseItem(System.Web.HttpContext context, string id, object lockId) { var expires = DateTime.Now.AddMinutes(base.SessionStateSection.Timeout.TotalMinutes); collection.Update(Query.And(Query.EQ("LockId", int.Parse(lockId.ToString())), Query.EQ("_id", id)), Update.Set("Locked", false).Set("Expires", expires)); SetSessionIdCookieExpires(context, expires); } /// <summary> /// 移除項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="lockId"></param> public void RemoveItem(System.Web.HttpContext context, string id, object lockId) { collection.Remove(Query.And(Query.EQ("LockId", int.Parse(lockId.ToString())), Query.EQ("_id", id))); SetSessionIdCookieExpires(context, DateTime.Now.AddDays(-1), true); } /// <summary> /// 重設項超時時間 /// </summary> /// <param name="context"></param> /// <param name="id"></param> public void ResetItemTimeout(System.Web.HttpContext context, string id) { var expires = DateTime.Now.AddMinutes(base.SessionStateSection.Timeout.TotalMinutes); collection.Update(Query.And(Query.EQ("_id", id)), Update.Set("Expires", expires)); SetSessionIdCookieExpires(context, expires); } /// <summary> /// 設置並釋放項 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="item"></param> /// <param name="lockId"></param> /// <param name="newItem"></param> public void SetAndReleaseItem(System.Web.HttpContext context, string id, System.Web.SessionState.SessionStateStoreData item, object lockId, bool newItem) { var session = default(MongoDBSessionDo); if (newItem) { session = new MongoDBSessionDo(); session.SessionId = id; session.Created = DateTime.Now; session.Expires = DateTime.Now.AddMinutes(base.SessionStateSection.Timeout.TotalMinutes); session.LockDate = DateTime.Now; session.LockId = 0; session.Timeout = item.Timeout; session.Locked = false; session.Flags = (int)SessionStateActions.None; session.SessionItems = Helper.Serialize((SessionStateItemCollection)item.Items); } else { session = collection.FindOne(Query.And(Query.EQ("_id", id), Query.EQ("LockId", int.Parse(lockId.ToString())))); session.Expires = DateTime.Now.AddMinutes(item.Timeout); session.SessionItems = Helper.Serialize((SessionStateItemCollection)item.Items); session.Locked = false; } collection.Save(session); SetSessionIdCookieExpires(context, session.Expires); } }
配置相關
<configSections> <section name="SessionProviderSettings" type="CustomSessionDemo.SessionProviderConfigurationSectionHandler, CustomSessionDemo"/> </configSections> <SessionProviderSettings> <SessionProfix>Test</SessionProfix> <IsSynchronousSessionIdTimeout>true</IsSynchronousSessionIdTimeout> <SessionStateStoreProviderBehaviorName>Mongo</SessionStateStoreProviderBehaviorName> <StorageConnectionString>mongodb://192.168.1.43:27017/TestSessionDB</StorageConnectionString> </SessionProviderSettings> <system.web> <sessionState mode="Custom" customProvider="SessionProvider" sessionIDManagerType="CustomSessionDemo.CustomSessionIDManager" timeout="20"> <providers> <add name="SessionProvider" type="CustomSessionDemo.CustomSessionStateStoreProvider, CustomSessionDemo"/> </providers> </sessionState>
執行效果