可靈活擴展的自定義Session狀態存儲驅動


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>

執行效果

image

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM