Статьи

Поймать тайм-ауты сессии, прежде чем они кусаются

Использование сессий для хранения пользовательской информации — очень удобная функция ASP.NET. Но это не без его темной стороны. Одна из основных трудностей заключается в том, что сессии могут неожиданно умереть. Это может произойти по ряду причин. Это может быть просто время ожидания. Сервер может решить, что ему нужно перезапустить приложение. Пользователь может очистить свои сеансовые куки. Там может быть солнечное пятно.

В легких случаях это может просто вызвать некоторые ошибки «Ссылка на объект не установлена ​​на Экземпляр объекта». В плохих случаях это может привести к повреждению данных, так как кто-то пытается обновить базовое хранилище данных на основе несуществующих данных. В любом случае, это можно предотвратить, используя немного кода.

Стратегия такова: во-первых, нам нужно создать класс SessionTimeoutWatcher, который, ну, в общем, следит за временем ожидания. Для работы этого класса требуется несколько частей информации, а именно: идентификатор текущей сессии, идентификатор сессии предыдущего запроса и доступ к некоторым переменным и событиям на уровне страницы.

Чтобы абстрагировать механизм хранения и извлечения идентификаторов сеансов, мы создадим небольшой интерфейс:

public interface ISessionContainer { /// <summary> /// Identifying string of the current requests' Session. /// </summary> string CurrentSessionId { get; set;} /// <summary> /// Identifying string of the previous requests' Session. /// </summary> string PreviousSessionId { get; set; } /// <summary> /// Flag determining if exceptions will be thrown upon Session timeout. /// </summary> bool EnforceSessionWatch { get; } } 

Я также добавил флаг, позволяющий контейнеру определить, действительно ли отслеживание сеанса действительно выполняется.

Далее нам нужен сам наблюдатель:

public class SessionTimeoutWatcher { private ISessionContainer sessionContainer; private Page page; /// <summary> /// Initializes and sets up the Session Timeout watcher. /// </summary> /// <param name="sessionContainer">Container session data used by watcher</param> /// <param name="currentPage">Page requested.</param> public SessionTimeoutWatcher(ISessionContainer sessionContainer, Page currentPage) { page = currentPage; this.sessionContainer = sessionContainer; page.Load += new EventHandler(setupSessionWatch); page.LoadComplete += new EventHandler(checkSession); } private void setupSessionWatch(object sender, EventArgs e) { sessionContainer.PreviousSessionId = sessionContainer.CurrentSessionId; sessionContainer.CurrentSessionId = page.Session.SessionID; } private void checkSession(object sender, EventArgs e) { //Make sure we must enforce this logic. if (sessionContainer.EnforceSessionWatch) { //Check the session ids to see if they match. //If previous ID is null or empty, it means the session is new, which is OK. if ( !string.IsNullOrEmpty(sessionContainer.PreviousSessionId) && sessionContainer.CurrentSessionId != sessionContainer.PreviousSessionId ) { throw new SessionExpiredException("Session ID has changed."); } //Check if the session has been reset. if ( sessionContainer.CurrentSessionId == sessionContainer.PreviousSessionId && page.Session.IsNewSession ) { throw new SessionExpiredException("Session has been reset."); } } } }
public class SessionTimeoutWatcher { private ISessionContainer sessionContainer; private Page page; /// <summary> /// Initializes and sets up the Session Timeout watcher. /// </summary> /// <param name="sessionContainer">Container session data used by watcher</param> /// <param name="currentPage">Page requested.</param> public SessionTimeoutWatcher(ISessionContainer sessionContainer, Page currentPage) { page = currentPage; this.sessionContainer = sessionContainer; page.Load += new EventHandler(setupSessionWatch); page.LoadComplete += new EventHandler(checkSession); } private void setupSessionWatch(object sender, EventArgs e) { sessionContainer.PreviousSessionId = sessionContainer.CurrentSessionId; sessionContainer.CurrentSessionId = page.Session.SessionID; } private void checkSession(object sender, EventArgs e) { //Make sure we must enforce this logic. if (sessionContainer.EnforceSessionWatch) { //Check the session ids to see if they match. //If previous ID is null or empty, it means the session is new, which is OK. if ( !string.IsNullOrEmpty(sessionContainer.PreviousSessionId) && sessionContainer.CurrentSessionId != sessionContainer.PreviousSessionId ) { throw new SessionExpiredException("Session ID has changed."); } //Check if the session has been reset. if ( sessionContainer.CurrentSessionId == sessionContainer.PreviousSessionId && page.Session.IsNewSession ) { throw new SessionExpiredException("Session has been reset."); } } } } 

Выше не слишком причудливо. Он просто берет ссылку на ISessionContainer и текущую страницу в конструкторе, а затем связывает себя с несколькими событиями страницы.

Ключевым методом является метод checkSession (object, EventArgs), который обрабатывает фактическую проверку. Чек двойной. Первый оператор if гарантирует, что идентификатор сеанса не изменился. Это будет означать, например, что пользователь очистил свои куки. Второй проверяет флаг IsNewSession сеанса, чтобы определить, был ли сеанс перезагружен, но имеет ли тот же идентификатор.

Если проверка недействительна, она генерирует пользовательское исключение, чтобы сообщить программе, что сеанс истек. Затем приложение может определить, что нужно делать, используя обработчики ошибок на уровне страницы или приложения.

Наконец, связать это на странице очень просто. Лучшее место для добавления этого и многих других глобальных функций — это базовый класс страниц, общий для всех страниц приложения.

public class PageBase : Page, ISessionContainer { public PageBase() { timeoutWatcher = new SessionTimeoutWatcher(this, this); } private SessionTimeoutWatcher timeoutWatcher; #region ISessionContainer Members public string CurrentSessionId { get { return (string)ViewState["__CurrentSessionId"]; } set { ViewState["__CurrentSessionId"] = value; } } public string PreviousSessionId { get { return (string)ViewState["__PreviousSessionId"]; } set { ViewState["__PreviousSessionId"] = value; } } public virtual bool EnforceSessionWatch { get { return true; } } #endregion }
public class PageBase : Page, ISessionContainer { public PageBase() { timeoutWatcher = new SessionTimeoutWatcher(this, this); } private SessionTimeoutWatcher timeoutWatcher; #region ISessionContainer Members public string CurrentSessionId { get { return (string)ViewState["__CurrentSessionId"]; } set { ViewState["__CurrentSessionId"] = value; } } public string PreviousSessionId { get { return (string)ViewState["__PreviousSessionId"]; } set { ViewState["__PreviousSessionId"] = value; } } public virtual bool EnforceSessionWatch { get { return true; } } #endregion } 

Здесь следует отметить одну вещь — метод EnforceSessionWatch был преднамеренно объявлен как виртуальный. Это позволяет разработчику легко переопределить его на данной странице, чтобы пропустить принудительное выполнение сеанса.

Наслаждайся и пни, если тебе нравится .

Наконец, вы можете скачать пример проекта, содержащий весь код, использованный выше, отсюда .