• 可灵活扩展的自定义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

  • 相关阅读:
    MTK手机默认音量大小调节工具
    问题:MTK手机软件开发平台中字串资源添加进去了,菜单也能用,但是菜单上的字符串显示不出来。
    GNU ARM汇编快速入门
    想成为嵌入式程序员应知道的0x10个基本问题
    学习MTK需要的环境,平台,资料
    BSP 概念解析
    作为程序员的苦恼
    浅谈程序员的职业规划
    入行三年回顾
    程序员谈“应用推广”
  • 原文地址:https://www.cnblogs.com/royding/p/Session.html
Copyright © 2020-2023  润新知