johnllao

July 8, 2009

Custom Sybase Database to persist Session state

Filed under: Uncategorized — johnllao @ 2:17 pm

In this blog I would like to share the SybaseSessionState I have implemented. Thanks to this article http://www.codeproject.com/KB/aspnet/ASPNET_session_in_MySql.aspx in CodeProject. I only did a slight changes to user Sybase as the data store.

 In your Web.config, you needed to use the following configuration.

    <sessionState cookieless="false" regenerateExpiredSessionId="true" mode="Custom" customProvider="SybaseSessionProvider" >
      <providers>
        <add name="SybaseSessionProvider"
             type="SybaseSessionStateStore"
             connectionStringName="YourConnectionStringName"
             writeExceptionsToEventLog="false" />
      </providers>
    </sessionState>

 Please see the code

    /// <summary>
    /// <remarks>
    /// The following table has to be created
    ///     CREATE TABLE sessions
    ///     (
    ///         SessionId       varchar(80)  NOT NULL,
    ///         ApplicationName varchar(255) NOT NULL,
    ///         Created         datetime     NOT NULL,
    ///         Expires         datetime     NOT NULL,
    ///         LockDate        datetime     NOT NULL,
    ///         LockId          int          NOT NULL,
    ///         Timeout         int          NOT NULL,
    ///         Locked          bit          NOT NULL,
    ///         SessionItems    text,
    ///         Flags           int          NOT NULL,
    ///         CONSTRAINT PKSessions PRIMARY KEY (SessionId, ApplicationName)
    ///      )
    /// </remarks>
    /// </summary>
    public sealed class SybaseSessionStateStore : SessionStateStoreProviderBase
    {
        private SessionStateSection pConfig = null;
        private string connectionString;
        private ConnectionStringSettings pConnectionStringSettings;
        private string eventSource = "SybaseSessionStateStore";
        private string eventLog = "Application";
        private string exceptionMessage = "An exception occurred. Please contact your administrator.";
        private string pApplicationName;

        private bool pWriteExceptionsToEventLog = false;
        /// <summary>
        /// If false, exceptions are thrown to the caller. If true, exceptions are written to the event log.
        /// </summary>
        public bool WriteExceptionsToEventLog
        {
            get { return pWriteExceptionsToEventLog; }
            set { pWriteExceptionsToEventLog = value; }
        }

        /// <summary>
        /// The ApplicationName property is used to differentiate sessions in the data source by application.
        /// </summary>
        public string ApplicationName
        {
            get { return pApplicationName; }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="config"></param>
        public override void Initialize(string name, NameValueCollection config)
        {
            // Initialize values from web.config.
            if (config == null)
                throw new ArgumentNullException("config");

            if (name == null || name.Length == 0)
                name = "SybaseSessionStateStore";

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Sybase Session State Store provider");
            }

            // Initialize the abstract base class.
            base.Initialize(name, config);

            // Initialize the ApplicationName property.
            pApplicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;

            // Get <sessionState> configuration element.
            System.Configuration.Configuration cfg = WebConfigurationManager.OpenWebConfiguration(ApplicationName);
            pConfig = (SessionStateSection)cfg.GetSection("system.web/sessionState");

            // Initialize connection string.
            pConnectionStringSettings = ConfigurationManager.ConnectionStrings[config["connectionStringName"]];

            if (pConnectionStringSettings == null || pConnectionStringSettings.ConnectionString.Trim() == "")
            {
                throw new ProviderException("Connection string cannot be blank.");
            }

            connectionString = pConnectionStringSettings.ConnectionString;

            // Initialize WriteExceptionsToEventLog
            pWriteExceptionsToEventLog = false;

            if (config["writeExceptionsToEventLog"] != null)
            {
                if (config["writeExceptionsToEventLog"].ToUpper() == "TRUE")
                    pWriteExceptionsToEventLog = true;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override void Dispose()
        {

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expireCallback"></param>
        /// <returns></returns>
        public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
        {
            return false;
        }

        /// <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)
        {
            // Serialize the SessionStateItemCollection as a string.
            string sessItems = Serialize((SessionStateItemCollection)item.Items);

            AseConnection conn = new AseConnection(connectionString);
            AseCommand cmd;
            AseCommand deleteCmd = null;

            if (newItem)
            {
                // AseCommand to clear an existing expired session if it exists.
                deleteCmd = new AseCommand("DELETE FROM sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND Expires < @Expires", conn);
                deleteCmd.Parameters.Add("@SessionId", AseDbType.VarChar, 80).Value = id;
                deleteCmd.Parameters.Add("@ApplicationName", AseDbType.VarChar, 255).Value = ApplicationName;
                deleteCmd.Parameters.Add("@Expires", AseDbType.DateTime).Value = DateTime.Now;

                // AseCommand to insert the new session item.
                cmd = new AseCommand("INSERT INTO sessions (SessionId, ApplicationName, Created, Expires, LockDate, LockId, Timeout, Locked, SessionItems, Flags) Values(@SessionId, @ApplicationName, @Created, @Expires, @LockDate, @LockId, @Timeout, @Locked, @SessionItems, @Flags)", conn);
                cmd.Parameters.Add("@SessionId", AseDbType.VarChar, 80).Value = id;
                cmd.Parameters.Add("@ApplicationName", AseDbType.VarChar, 255).Value = ApplicationName;
                cmd.Parameters.Add("@Created", AseDbType.DateTime).Value = DateTime.Now;
                cmd.Parameters.Add("@Expires", AseDbType.DateTime).Value = DateTime.Now.AddMinutes((Double)item.Timeout);
                cmd.Parameters.Add("@LockDate", AseDbType.DateTime).Value = DateTime.Now;
                cmd.Parameters.Add("@LockId", AseDbType.Integer).Value = 0;
                cmd.Parameters.Add("@Timeout", AseDbType.Integer).Value = item.Timeout;
                cmd.Parameters.Add("@Locked", AseDbType.Integer).Value = 0;
                cmd.Parameters.Add("@SessionItems", AseDbType.Text).Value = sessItems;
                cmd.Parameters.Add("@Flags", AseDbType.Integer).Value = 0;
            }
            else
            {
                // AseCommand to update the existing session item.
                cmd = new AseCommand("UPDATE sessions SET Expires = @Expires, SessionItems = @SessionItems, Locked = @Locked WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId", conn);
                cmd.Parameters.Add("@Expires", AseDbType.DateTime).Value = DateTime.Now.AddMinutes((Double)item.Timeout);
                cmd.Parameters.Add("@SessionItems", AseDbType.Text).Value = sessItems;
                cmd.Parameters.Add("@Locked", AseDbType.Integer).Value = 0;
                cmd.Parameters.Add("@SessionId", AseDbType.VarChar, 80).Value = id;
                cmd.Parameters.Add("@ApplicationName", AseDbType.VarChar, 255).Value = ApplicationName;
                cmd.Parameters.Add("@LockId", AseDbType.Integer).Value = lockId;
            }

            try
            {
                conn.Open();

                if (deleteCmd != null)
                    deleteCmd.ExecuteNonQuery();

                cmd.ExecuteNonQuery();
            }
            catch (AseException e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "SetAndReleaseItemExclusive");
                    throw new ProviderException(exceptionMessage);
                }
                else
                    throw e;
            }
            finally
            {
                conn.Close();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="locked"></param>
        /// <param name="lockAge"></param>
        /// <param name="lockId"></param>
        /// <param name="actionFlags"></param>
        /// <returns></returns>
        public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags)
        {
            return GetSessionStoreItem(false, context, id, out locked, out lockAge, out lockId, out actionFlags);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="locked"></param>
        /// <param name="lockAge"></param>
        /// <param name="lockId"></param>
        /// <param name="actionFlags"></param>
        /// <returns></returns>
        public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags)
        {
            return GetSessionStoreItem(true, context, id, out locked, out lockAge, out lockId, out actionFlags);
        }

        /// <summary>
        /// GetSessionStoreItem is called by both the GetItem and GetItemExclusive methods. GetSessionStoreItem retrieves the 
        /// session data from the data source. If the lockRecord parameter is true (in the case of GetItemExclusive), then GetSessionStoreItem 
        /// locks the record and sets a new LockId and LockDate.
        /// </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="actionFlags"></param>
        /// <returns></returns>
        private SessionStateStoreData GetSessionStoreItem(bool lockRecord, HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags)
        {
            // Initial values for return value and out parameters.
            SessionStateStoreData item = null;
            lockAge = TimeSpan.Zero;
            lockId = null;
            locked = false;
            actionFlags = 0;

            AseConnection conn = new AseConnection(connectionString);
            AseCommand cmd = null;
            AseDataReader reader = null;

            // Datetime to check if current session item is expired.
            DateTime expires;

            // String to hold serialized SessionStateItemCollection.
            string serializedItems = "";

            // True if a record is found in the database.
            bool foundRecord = false;

            // True if the returned session item is expired and needs to be deleted.
            bool deleteData = false;

            // Timeout value from the data store.
            int timeout = 0;

            try
            {
                conn.Open();

                // lockRecord is true when called from GetItemExclusive and  false when called from GetItem. 
                // Obtain a lock if possible. Ignore the record if it is expired.
                if (lockRecord)
                {
                    cmd = new AseCommand("UPDATE sessions SET Locked = @Locked1, LockDate = @LockDate WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND Locked = @Locked2 AND Expires > @Expires", conn);
                    cmd.Parameters.Add("@Locked1", AseDbType.Integer).Value = 1;
                    cmd.Parameters.Add("@LockDate", AseDbType.DateTime).Value = DateTime.Now;
                    cmd.Parameters.Add("@SessionId", AseDbType.VarChar, 80).Value = id;
                    cmd.Parameters.Add("@ApplicationName", AseDbType.VarChar, 255).Value = ApplicationName;
                    cmd.Parameters.Add("@Locked2", AseDbType.Integer).Value = 0;
                    cmd.Parameters.Add("@Expires", AseDbType.DateTime).Value = DateTime.Now;

                    if (cmd.ExecuteNonQuery() == 0)
                    {
                        // No record was updated because the record was locked or not found.
                        locked = true;
                    }
                    else
                    {
                        // The record was updated.
                        locked = false;
                    }
                }

                // Retrieve the current session item information.
                cmd = new AseCommand("SELECT Expires, SessionItems, LockId, LockDate, Flags, Timeout FROM sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName", conn);
                cmd.Parameters.Add("@SessionId", AseDbType.VarChar, 80).Value = id;
                cmd.Parameters.Add("@ApplicationName", AseDbType.VarChar, 255).Value = ApplicationName;

                // Retrieve session item data from the data source.
                reader = cmd.ExecuteReader(CommandBehavior.SingleRow);
                while (reader.Read())
                {
                    expires = reader.GetDateTime(0);

                    if (expires < DateTime.Now)
                    {
                        // The record was expired. Mark it as not locked.
                        locked = false;
                        // The session was expired. Mark the data for deletion.
                        deleteData = true;
                    }
                    else
                        foundRecord = true;

                    serializedItems = reader.GetString(1);
                    lockId = reader.GetInt32(2);
                    lockAge = DateTime.Now.Subtract(reader.GetDateTime(3));
                    actionFlags = (SessionStateActions)reader.GetInt32(4);
                    timeout = reader.GetInt32(5);
                }
                reader.Close();

                // If the returned session item is expired, 
                // delete the record from the data source.
                if (deleteData)
                {
                    cmd = new AseCommand("DELETE FROM sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName", conn);
                    cmd.Parameters.Add("@SessionId", AseDbType.VarChar, 80).Value = id;
                    cmd.Parameters.Add("@ApplicationName", AseDbType.VarChar, 255).Value = ApplicationName;

                    cmd.ExecuteNonQuery();
                }

                // The record was not found. Ensure that locked is false.
                if (!foundRecord)
                    locked = false;

                // If the record was found and you obtained a lock, then set 
                // the lockId, clear the actionFlags,
                // and create the SessionStateStoreItem to return.
                if (foundRecord && !locked)
                {
                    lockId = (int)lockId + 1;

                    cmd = new AseCommand("UPDATE sessions SET LockId = @LockId, Flags = 0 WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName", conn);
                    cmd.Parameters.Add("@LockId", AseDbType.Integer).Value = lockId;
                    cmd.Parameters.Add("@SessionId", AseDbType.VarChar, 80).Value = id;
                    cmd.Parameters.Add("@ApplicationName", AseDbType.VarChar, 255).Value = ApplicationName;

                    cmd.ExecuteNonQuery();

                    // If the actionFlags parameter is not InitializeItem, 
                    // deserialize the stored SessionStateItemCollection.
                    if (actionFlags == SessionStateActions.InitializeItem)
                        item = CreateNewStoreData(context, pConfig.Timeout.Minutes);
                    else
                        item = Deserialize(context, serializedItems, timeout);
                }
            }
            catch (AseException e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetSessionStoreItem");
                    throw new ProviderException(exceptionMessage);
                }
                else
                    throw e;
            }
            finally
            {
                if (reader != null) { reader.Close(); }
                conn.Close();
            }

            return item;
        }

        /// <summary>
        /// Serialize is called by the SetAndReleaseItemExclusive method to convert the SessionStateItemCollection into a Base64 string to 
        /// be stored in an Access Memo field.
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private string Serialize(SessionStateItemCollection items)
        {
            MemoryStream ms = new MemoryStream();
            BinaryWriter writer = new BinaryWriter(ms);

            if (items != null)
                items.Serialize(writer);

            writer.Close();

            return Convert.ToBase64String(ms.ToArray());
        }

        /// <summary>
        /// DeSerialize is called by the GetSessionStoreItem method to convert the Base64 string stored in the Access Memo field to a 
        /// SessionStateItemCollection.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="serializedItems"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        private SessionStateStoreData Deserialize(HttpContext context, string serializedItems, int timeout)
        {
            MemoryStream ms = new MemoryStream(Convert.FromBase64String(serializedItems));

            SessionStateItemCollection sessionItems = new SessionStateItemCollection();

            if (ms.Length > 0)
            {
                BinaryReader reader = new BinaryReader(ms);
                sessionItems = SessionStateItemCollection.Deserialize(reader);
            }

            return new SessionStateStoreData(sessionItems,
              SessionStateUtility.GetSessionStaticObjects(context),
              timeout);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="lockId"></param>
        public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
        {
            AseConnection conn = new AseConnection(connectionString);
            AseCommand cmd = new AseCommand("UPDATE sessions SET Locked = 0, Expires = @Expires WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId", conn);
            cmd.Parameters.Add("@Expires", AseDbType.DateTime).Value = DateTime.Now.AddMinutes(pConfig.Timeout.Minutes);
            cmd.Parameters.Add("@SessionId", AseDbType.VarChar, 80).Value = id;
            cmd.Parameters.Add("@ApplicationName", AseDbType.VarChar, 255).Value = ApplicationName;
            cmd.Parameters.Add("@LockId", AseDbType.Integer).Value = lockId;

            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (AseException e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ReleaseItemExclusive");
                    throw new ProviderException(exceptionMessage);
                }
                else
                    throw e;
            }
            finally
            {
                conn.Close();
            }
        }

        /// <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)
        {
            AseConnection conn = new AseConnection(connectionString);
            AseCommand cmd = new AseCommand("DELETE FROM sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId", conn);
            cmd.Parameters.Add("@SessionId", AseDbType.VarChar, 80).Value = id;
            cmd.Parameters.Add("@ApplicationName", AseDbType.VarChar, 255).Value = ApplicationName;
            cmd.Parameters.Add("@LockId", AseDbType.Integer).Value = lockId;

            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (AseException e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "RemoveItem");
                    throw new ProviderException(exceptionMessage);
                }
                else
                    throw e;
            }
            finally
            {
                conn.Close();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="timeout"></param>
        public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
        {
            AseConnection conn = new AseConnection(connectionString);
            AseCommand cmd = new AseCommand("INSERT INTO sessions (SessionId, ApplicationName, Created, Expires, LockDate, LockId, Timeout, Locked, SessionItems, Flags) Values(@SessionId, @ApplicationName, @Created, @Expires, @LockDate, @LockId, @Timeout, @Locked, @SessionItems, @Flags)", conn);
            cmd.Parameters.Add("@SessionId", AseDbType.VarChar, 80).Value = id;
            cmd.Parameters.Add("@ApplicationName", AseDbType.VarChar, 255).Value = ApplicationName;
            cmd.Parameters.Add("@Created", AseDbType.DateTime).Value = DateTime.Now;
            cmd.Parameters.Add("@Expires", AseDbType.DateTime).Value = DateTime.Now.AddMinutes((Double)timeout);
            cmd.Parameters.Add("@LockDate", AseDbType.DateTime).Value = DateTime.Now;
            cmd.Parameters.Add("@LockId", AseDbType.Integer).Value = 0;
            cmd.Parameters.Add("@Timeout", AseDbType.Integer).Value = timeout;
            cmd.Parameters.Add("@Locked", AseDbType.Integer).Value = 0;
            cmd.Parameters.Add("@SessionItems", AseDbType.Text).Value = "";
            cmd.Parameters.Add("@Flags", AseDbType.Integer).Value = 1;

            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (AseException e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "CreateUninitializedItem");
                    throw new ProviderException(exceptionMessage);
                }
                else
                    throw e;
            }
            finally
            {
                conn.Close();
            }
        }

        /// <summary>
        /// 
        /// </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>
        public override void ResetItemTimeout(HttpContext context,
                                              string id)
        {
            AseConnection conn = new AseConnection(connectionString);
            AseCommand cmd = new AseCommand("UPDATE sessions SET Expires = @Expires WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName", conn);
            cmd.Parameters.Add("@Expires", AseDbType.DateTime).Value = DateTime.Now.AddMinutes(pConfig.Timeout.Minutes);
            cmd.Parameters.Add("@SessionId", AseDbType.VarChar, 80).Value = id;
            cmd.Parameters.Add("@ApplicationName", AseDbType.VarChar, 255).Value = ApplicationName;

            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (AseException e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ResetItemTimeout");
                    throw new ProviderException(exceptionMessage);
                }
                else
                    throw e;
            }
            finally
            {
                conn.Close();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public override void InitializeRequest(HttpContext context)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public override void EndRequest(HttpContext context)
        {
        }

        /// <summary>
        /// WriteToEventLog
        /// This is a helper function that writes exception detail to the event log. Exceptions are written to the event log as a security
        /// measure to ensure private database details are not returned to browser. If a method does not return a status or Boolean
        /// indicating the action succeeded or failed, the caller also throws a generic exception.
        /// </summary>
        /// <param name="e"></param>
        /// <param name="action"></param>
        private void WriteToEventLog(Exception e, string action)
        {
            EventLog log = new EventLog();
            log.Source = eventSource;
            log.Log = eventLog;

            string message = "An exception occurred communicating with the data source.\n\n";
            message += "Action: " + action + "\n\n";
            message += "Exception: " + e.ToString();

            log.WriteEntry(message);
        }
    }

No Comments Yet »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

You must be logged in to post a comment.

Blog at WordPress.com.