Asp.net 自定义CustomerSession 存放到Redis中

首先,引用 Redis 操作驱动组件:StackExchange.Redis.dll。

继承SessionStateStoreProviderBase 类, 实现方法:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.SessionState;

namespace KingSessionStateProvider
{
    /// <summary>
    /// 自定义Session类
    /// </summary>
    public class KingSessionStateProvider : SessionStateStoreProviderBase
    {
        /// <summary>
        /// 获取配置文件的设置的默认超时时间
        /// </summary>
        private static TimeSpan _expiresTime;

        /// <summary>
        /// 获取Web.config 在sessionState设置的超时时间
        /// </summary>
        static KingSessionStateProvider()
        {
            System.Web.Configuration.SessionStateSection sessionStateSection = (System.Web.Configuration.SessionStateSection)System.Configuration.ConfigurationManager.GetSection("system.web/sessionState");
            _expiresTime = sessionStateSection.Timeout;
        }


        /// <summary>
        /// 创建要用于当前请求的新 SessionStateStoreData 对象。
        /// </summary>
        /// <param name="context"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public override SessionStateStoreData CreateNewStoreData(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(HttpContext context, string id, int timeout)
        {
            /* 
                TODO: 保存数据库中
             */
            var session = new ASPStateTempSessions
            {
                Created = DateTime.Now,
                Expires = DateTime.Now.AddMinutes(timeout),
                Flags = (int)SessionStateActions.InitializeItem,
                LockDate = DateTime.Now,
                Locked = false,
                SessionId = id,
                LockId = 0,
                Timeout = timeout
            };
            RedisDbHelper.Set(id, session);
        }



        /// <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(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            return DoGet(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(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            return DoGet(true, context, id, out locked, out lockAge, out lockId, out actions);
        }


        /// <summary>
        /// 获取Session的值
        /// </summary>
        /// <param name="isExclusive"></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 SessionStateStoreData DoGet(bool isExclusive, HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {

            // 设置初始值
            var item = default(SessionStateStoreData);
            lockAge = TimeSpan.Zero;
            lockId = null;
            locked = false;
            actions = 0;

            // 如果数据存储区中未找到任何会话项数据,则GetItemExclusive 方法将 locked 输出参数设置为false,并返回 null。
            // 这将导致 SessionStateModule调用 CreateNewStoreData 方法来为请求创建一个新的SessionStateStoreData 对象。
            var session = RedisDbHelper.Get<ASPStateTempSessions>(id);
            if (session == null)
            {
                return null;
            }

            // 判断session是否是ReadOnly 模式,不是readonly模式得判断是否锁住
            if (isExclusive)
            {
                // 如果在数据存储区中找到会话项数据但该数据已锁定,则GetItemExclusive 方法将 locked 输出参数设置为true,
                // 将 lockAge 输出参数设置为当前日期和时间与该项锁定日期和时间的差,将 lockId 输出参数设置为从数据存储区中检索的锁定标识符,并返回 nul
                if (session.Locked)
                {
                    locked = true;
                    lockAge = session.LockDate - DateTime.Now;
                    lockId = session.LockId;
                    return null;
                }
            }

            // 判断是否过期
            if (session.Expires < DateTime.Now)
            {
                RedisDbHelper.Remove(id);   //移除条目
                return null;
            }

            // 处理值
            lockId = lockId == null ? 0 : (int)lockId + 1;
            session.Flags = (int)SessionStateActions.None;
            session.LockId = Convert.ToInt32(lockId);

            // 获取timeout
            var timeout = actions == SessionStateActions.InitializeItem ? _expiresTime.TotalMinutes : session.Timeout;

            // 获取SessionStateItemCollection 
            SessionStateItemCollection sessionStateItemCollection = null;

            // 获取Session的值
            if (actions == SessionStateActions.None && (session.SessionItem != null))
            {
                sessionStateItemCollection = Deserialize(session.SessionItem);
            }

            item = new SessionStateStoreData(sessionStateItemCollection ?? new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), (int)timeout);

            return item;
        }




        /// <summary>
        /// 释放对会话数据存储区中项的锁定。
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="lockId"></param>
        public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
        {
            var session = RedisDbHelper.Get<ASPStateTempSessions>(id);
            if (session == null)
            {
                return;
            }
            // 把locked设置为false
            session.Locked = false;
            session.Expires = DateTime.Now + _expiresTime;
            var bol = RedisDbHelper.Set(id, session);
        }

        /// <summary>
        /// 删除会话数据存储区中的项数据。
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="lockId"></param>
        /// <param name="item"></param>
        public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
        {
            //Delete
            if (RedisDbHelper.Exists(id))
            {
                RedisDbHelper.Remove(id);
            }
        }


        /// <summary>
        /// 设置超时时间
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        public override void ResetItemTimeout(HttpContext context, string id)
        {
            var session = RedisDbHelper.Get<ASPStateTempSessions>(id);

            if (session == null)
            {
                return;
            }
            session.Expires = DateTime.Now + _expiresTime;
            var bol = RedisDbHelper.Set(id, session);
        }


        /// <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(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
        {
            // 判断是否是新建,如果是新建则和CreateUninitializedItem不同在于Timeout和有初始值。
            if (newItem)
            {
                // 1. Clear an existing expired session if it exists.
                if (RedisDbHelper.Exists(id))
                {
                    RedisDbHelper.Remove(id);
                }

                // 2. 保存到数据库中 注意须设置过期时间
                // Expires = DateTime.Now.AddMinutes(item.Timeout) 设置过期时间
                var session = new ASPStateTempSessions
                {
                    Created = DateTime.Now,
                    Expires = DateTime.Now.AddMinutes(item.Timeout),
                    Flags = (int)SessionStateActions.None,
                    LockDate = DateTime.Now,
                    Locked = false,
                    SessionId = id,
                    LockId = 0,
                    Timeout = item.Timeout,
                    SessionItem = Serialize((SessionStateItemCollection)item.Items)
                    //SessionItem = (SessionStateItemCollection)item.Items
                };
                var bol = RedisDbHelper.Set(id, session);
            }
            else// 释放锁定的项并设置Session的值
            {
                /*
                   if  Find Session By Session return null
                   then retrun null
                   else set session value
                Eg:
                session.Expires = DateTime.Now.AddMinutes(item.Timeout);
                session.Locked = false;
                session.SessionItem = Serialize((SessionStateItemCollection)item.Items);

                */

                var session = RedisDbHelper.Get<ASPStateTempSessions>(id);
                if (session == null)
                {
                    return;
                }

                session.Expires = DateTime.Now.AddMinutes(item.Timeout);
                session.Locked = false;
                session.LockId = Convert.ToInt32(lockId);
                session.SessionItem = Serialize((SessionStateItemCollection)item.Items);
                //session.SessionItem = (SessionStateItemCollection)item.Items;
                var bol = RedisDbHelper.Set(id, session);
            }
        }


        #region 序列化反序列化Session的值 
        /// <summary>
        /// 反序列化Session的数据
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public SessionStateItemCollection Deserialize(string item)
        {
            byte[] bts = HexToByte(item);
            MemoryStream stream = new MemoryStream(bts);
            //MemoryStream stream = new MemoryStream(System.Text.Encoding.Unicode.GetBytes(item));
            SessionStateItemCollection collection = new SessionStateItemCollection();
            if (stream.Length > 0)
            {
                BinaryReader reader = new BinaryReader(stream);
                collection = SessionStateItemCollection.Deserialize(reader);
            }
            return collection;
        }

        /// <summary>
        /// 将byte数组转换为16进制字符串
        /// </summary>
        /// <param name="bytes">要转换的数组</param>
        /// <param name="iStart">数组下标</param>
        /// <param name="iLength">长度</param>
        /// <returns></returns>
        public static string ToHexString(byte[] bytes) // 0xae00cf => "AE00CF "
        {
            string hexString = string.Empty;
            if (bytes != null)
            {
                StringBuilder strB = new StringBuilder();
                for (int i = 0; i < bytes.Length; i++)
                {
                    strB.Append(bytes[i].ToString("X2"));
                }
                hexString = strB.ToString();
            }
            return hexString;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="hexString"></param>
        /// <param name="discarded"></param>
        /// <returns></returns>
        public static byte[] GetBytes(string hexString, out int discarded)
        {
            discarded = 0;
            string newString = "";
            char c;
            // remove all none A-F, 0-9, characters
            for (int i = 0; i < hexString.Length; i++)
            {
                c = hexString[i];
                if (Uri.IsHexDigit(c))
                    newString += c;
                else
                    discarded++;
            }
            // if odd number of characters, discard last character
            if (newString.Length % 2 != 0)
            {
                discarded++;
                newString = newString.Substring(0, newString.Length - 1);
            }

            return HexToByte(newString);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="hexString"></param>
        /// <returns></returns>
        public static byte[] HexToByte(string hexString)
        {
            if (string.IsNullOrEmpty(hexString))
            {
                hexString = "00";
            }
            byte[] returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
                returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
            return returnBytes;
        }


        /// <summary>
        /// 序列化Session的数据
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        public string Serialize(SessionStateItemCollection items)
        {
            MemoryStream ms = new MemoryStream();
            BinaryWriter writer = new BinaryWriter(ms);
            if (items != null)
                items.Serialize(writer);
            writer.Close();

            //return  System.Text.Encoding.Unicode.GetString(ms.ToArray());
            //return Convert.ToBase64String(ms.ToArray());
            return ToHexString(ms.ToArray());
        }

        #endregion

        public override void Dispose()
        {

        }

        /// <summary>
        /// 设置对 Global.asax 文件中定义的 Session_OnEnd 事件的 SessionStateItemExpireCallback 委托的引用
        /// </summary>
        /// <param name="expireCallback"></param>
        /// <returns></returns>
        public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
        {
            return true;
        }

        /// <summary>
        /// 由 SessionStateModule 对象调用,以便进行每次请求初始化。
        /// </summary>
        /// <param name="context"></param>
        public override void InitializeRequest(HttpContext context)
        {

        }
        public override void EndRequest(HttpContext context)
        {

        }
    }
}

using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text;using System.Web;using System.Web.SessionState;

namespace KingSessionStateProvider{ /// <summary> /// 金康自定义Session类 /// </summary> public class KingSessionStateProvider : SessionStateStoreProviderBase { /// <summary> /// 获取配置文件的设置的默认超时时间 /// </summary> private static TimeSpan _expiresTime;

/// <summary> /// 获取Web.config 在sessionState设置的超时时间 /// </summary> static KingSessionStateProvider() { System.Web.Configuration.SessionStateSection sessionStateSection = (System.Web.Configuration.SessionStateSection)System.Configuration.ConfigurationManager.GetSection("system.web/sessionState"); _expiresTime = sessionStateSection.Timeout; }

/// <summary> /// 创建要用于当前请求的新 SessionStateStoreData 对象。 /// </summary> /// <param name="context"></param> /// <param name="timeout"></param> /// <returns></returns> public override SessionStateStoreData CreateNewStoreData(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(HttpContext context, string id, int timeout) { /* TODO: 保存数据库中 */ var session = new ASPStateTempSessions { Created = DateTime.Now, Expires = DateTime.Now.AddMinutes(timeout), Flags = (int)SessionStateActions.InitializeItem, LockDate = DateTime.Now, Locked = false, SessionId = id, LockId = 0, Timeout = timeout }; RedisDbHelper.Set(id, session); }

/// <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(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions) { return DoGet(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(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions) { return DoGet(true, context, id, out locked, out lockAge, out lockId, out actions); }

/// <summary> /// 获取Session的值 /// </summary> /// <param name="isExclusive"></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 SessionStateStoreData DoGet(bool isExclusive, HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions) {

// 设置初始值 var item = default(SessionStateStoreData); lockAge = TimeSpan.Zero; lockId = null; locked = false; actions = 0;

// 如果数据存储区中未找到任何会话项数据,则GetItemExclusive 方法将 locked 输出参数设置为false,并返回 null。 // 这将导致 SessionStateModule调用 CreateNewStoreData 方法来为请求创建一个新的SessionStateStoreData 对象。 var session = RedisDbHelper.Get<ASPStateTempSessions>(id); if (session == null) { return null; }

// 判断session是否是ReadOnly 模式,不是readonly模式得判断是否锁住 if (isExclusive) { // 如果在数据存储区中找到会话项数据但该数据已锁定,则GetItemExclusive 方法将 locked 输出参数设置为true, // 将 lockAge 输出参数设置为当前日期和时间与该项锁定日期和时间的差,将 lockId 输出参数设置为从数据存储区中检索的锁定标识符,并返回 nul if (session.Locked) { locked = true; lockAge = session.LockDate - DateTime.Now; lockId = session.LockId; return null; } }

// 判断是否过期 if (session.Expires < DateTime.Now) { RedisDbHelper.Remove(id); //移除条目 return null; }

// 处理值 lockId = lockId == null ? 0 : (int)lockId + 1; session.Flags = (int)SessionStateActions.None; session.LockId = Convert.ToInt32(lockId);

// 获取timeout var timeout = actions == SessionStateActions.InitializeItem ? _expiresTime.TotalMinutes : session.Timeout;

// 获取SessionStateItemCollection SessionStateItemCollection sessionStateItemCollection = null;

// 获取Session的值 if (actions == SessionStateActions.None && (session.SessionItem != null)) { sessionStateItemCollection = Deserialize(session.SessionItem); }

item = new SessionStateStoreData(sessionStateItemCollection ?? new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), (int)timeout);

return item; }

/// <summary> /// 释放对会话数据存储区中项的锁定。 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="lockId"></param> public override void ReleaseItemExclusive(HttpContext context, string id, object lockId) { var session = RedisDbHelper.Get<ASPStateTempSessions>(id); if (session == null) { return; } // 把locked设置为false session.Locked = false; session.Expires = DateTime.Now + _expiresTime; var bol = RedisDbHelper.Set(id, session); }

/// <summary> /// 删除会话数据存储区中的项数据。 /// </summary> /// <param name="context"></param> /// <param name="id"></param> /// <param name="lockId"></param> /// <param name="item"></param> public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item) { //Delete if (RedisDbHelper.Exists(id)) { RedisDbHelper.Remove(id); } }

/// <summary> /// 设置超时时间 /// </summary> /// <param name="context"></param> /// <param name="id"></param> public override void ResetItemTimeout(HttpContext context, string id) { var session = RedisDbHelper.Get<ASPStateTempSessions>(id);

if (session == null) { return; } session.Expires = DateTime.Now + _expiresTime; var bol = RedisDbHelper.Set(id, session); }

/// <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(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem) { // 判断是否是新建,如果是新建则和CreateUninitializedItem不同在于Timeout和有初始值。 if (newItem) { // 1. Clear an existing expired session if it exists. if (RedisDbHelper.Exists(id)) { RedisDbHelper.Remove(id); }

// 2. 保存到数据库中 注意须设置过期时间 // Expires = DateTime.Now.AddMinutes(item.Timeout) 设置过期时间 var session = new ASPStateTempSessions { Created = DateTime.Now, Expires = DateTime.Now.AddMinutes(item.Timeout), Flags = (int)SessionStateActions.None, LockDate = DateTime.Now, Locked = false, SessionId = id, LockId = 0, Timeout = item.Timeout, SessionItem = Serialize((SessionStateItemCollection)item.Items) //SessionItem = (SessionStateItemCollection)item.Items }; var bol = RedisDbHelper.Set(id, session); } else// 释放锁定的项并设置Session的值 { /* if Find Session By Session return null then retrun null else set session value Eg: session.Expires = DateTime.Now.AddMinutes(item.Timeout); session.Locked = false; session.SessionItem = Serialize((SessionStateItemCollection)item.Items);

*/

var session = RedisDbHelper.Get<ASPStateTempSessions>(id); if (session == null) { return; }

session.Expires = DateTime.Now.AddMinutes(item.Timeout); session.Locked = false; session.LockId = Convert.ToInt32(lockId); session.SessionItem = Serialize((SessionStateItemCollection)item.Items); //session.SessionItem = (SessionStateItemCollection)item.Items; var bol = RedisDbHelper.Set(id, session); } }

#region 序列化反序列化Session的值 /// <summary> /// 反序列化Session的数据 /// </summary> /// <param name="item"></param> /// <returns></returns> public SessionStateItemCollection Deserialize(string item) { byte[] bts = HexToByte(item); MemoryStream stream = new MemoryStream(bts); //MemoryStream stream = new MemoryStream(System.Text.Encoding.Unicode.GetBytes(item)); SessionStateItemCollection collection = new SessionStateItemCollection(); if (stream.Length > 0) { BinaryReader reader = new BinaryReader(stream); collection = SessionStateItemCollection.Deserialize(reader); } return collection; }

/// <summary> /// 将byte数组转换为16进制字符串 /// </summary> /// <param name="bytes">要转换的数组</param> /// <param name="iStart">数组下标</param> /// <param name="iLength">长度</param> /// <returns></returns> public static string ToHexString(byte[] bytes) // 0xae00cf => "AE00CF " { string hexString = string.Empty; if (bytes != null) { StringBuilder strB = new StringBuilder(); for (int i = 0; i < bytes.Length; i++) { strB.Append(bytes[i].ToString("X2")); } hexString = strB.ToString(); } return hexString; } /// <summary> /// /// </summary> /// <param name="hexString"></param> /// <param name="discarded"></param> /// <returns></returns> public static byte[] GetBytes(string hexString, out int discarded) { discarded = 0; string newString = ""; char c; // remove all none A-F, 0-9, characters for (int i = 0; i < hexString.Length; i++) { c = hexString[i]; if (Uri.IsHexDigit(c)) newString += c; else discarded++; } // if odd number of characters, discard last character if (newString.Length % 2 != 0) { discarded++; newString = newString.Substring(0, newString.Length - 1); }

return HexToByte(newString); }

/// <summary> /// /// </summary> /// <param name="hexString"></param> /// <returns></returns> public static byte[] HexToByte(string hexString) { if (string.IsNullOrEmpty(hexString)) { hexString = "00"; } byte[] returnBytes = new byte[hexString.Length / 2]; for (int i = 0; i < returnBytes.Length; i++) returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16); return returnBytes; }

/// <summary> /// 序列化Session的数据 /// </summary> /// <param name="items"></param> /// <returns></returns> public string Serialize(SessionStateItemCollection items) { MemoryStream ms = new MemoryStream(); BinaryWriter writer = new BinaryWriter(ms); if (items != null) items.Serialize(writer); writer.Close();

//return System.Text.Encoding.Unicode.GetString(ms.ToArray()); //return Convert.ToBase64String(ms.ToArray()); return ToHexString(ms.ToArray()); }

#endregion

public override void Dispose() {

}

/// <summary> /// 设置对 Global.asax 文件中定义的 Session_OnEnd 事件的 SessionStateItemExpireCallback 委托的引用 /// </summary> /// <param name="expireCallback"></param> /// <returns></returns> public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback) { return true; }

/// <summary> /// 由 SessionStateModule 对象调用,以便进行每次请求初始化。 /// </summary> /// <param name="context"></param> public override void InitializeRequest(HttpContext context) {

} public override void EndRequest(HttpContext context) {

} }}