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

posted on 2014-05-12 14:01  hellsoul86  阅读(2563)  评论(9编辑  收藏  举报