"HttpContext.Current.Session" против Global.asax "this.Session"

Недавно, работая над некоторым кодом для проекта ASP.NET на работе. Нам понадобилось отслеживание, чтобы использовать базовые показатели активности пользователя (количество просмотров страницы и т.д.), Мы будем отслеживать их в Session, а затем сохранять данные к DB через Session_End в Global.asax.

Я начал взламывать, исходный код работал нормально, обновляя БД при каждой загрузке страницы. Я хотел бы удалить этот БД по каждому запросу и просто полагаться на Session_End для хранения всех данных.

Весь код отслеживания инкапсулируется в класс Tracker, включая свойства, которые по существу переносят переменные сеанса.

Проблема заключается в том, что когда я выполнил Tracker.Log() в методе Session_End, HttpContext.Current.Session в коде Tracker с ошибкой NullReferenceException. Теперь это имеет смысл, поскольку HttpContext всегда относится к текущему запросу и, конечно, в Session_End, запроса нет.

Я знаю, что Global.asax имеет свойство Session, которое возвращает HttpSessionState, который на самом деле работает нормально (я ввел его в трекер).

Но мне любопытно, как, черт возьми, я могу получить ссылку на объект HttpSessionState, используемый Global.asax извне Global.asax?

Спасибо заранее, ребята, я ценю вход.:)

Ответ 1

Global.asax реализует HttpApplication - это то, о чем вы говорите, когда вы вызываете этот изнутри.

Документация MSDN для HttpApplication содержит сведения о том, как вы можете получить ее в HttpHandler, например, и затем получить доступ к различные свойства на нем.

ОДНАКО

Ваше приложение может создавать несколько экземпляров HttpApplication для обработки параллельных запросов, и эти экземпляры могут быть повторно использованы, поэтому просто подбирать их как-то не собирается, чтобы гарантировать, что у вас есть правильный.

Я также добавил бы примечание о предостережении - если ваше приложение выйдет из строя, нет никакой гарантии, что session_end будет вызван, и вы потеряете все данные во всех сеансах, что явно не очень хорошо.

Я согласен с тем, что регистрация на каждой странице, вероятно, не является отличной идеей, но, возможно, на полпути с некоторыми асинхронными протоколированиями - вы записываете данные в класс журнала, которые время от времени записывают данные, которые вы после - все еще не на 100%, если приложение выйдет из строя, но вы вряд ли потеряете все.

Ответ 2

Чтобы лучше ответить на исходный вопрос:

Фон

Каждый запрос одной страницы закручивает новый объект Session, а затем раздувает его из хранилища сеансов. Для этого он использует файл cookie, предоставленный клиентом, или специальную конструкцию пути (для сессий без cooki). С помощью этого идентификатора сеанса он обращается к хранилищу сеансов и десериализует (именно поэтому всем провайдерам, но InProc должен быть Serializable) новый объект сеанса.

В случае провайдера InProc просто передает вам ссылку, которую он хранит в HttpCache, с помощью идентификатора сеанса. Именно поэтому поставщик InProc сбрасывает состояние сеанса, когда AppDomain перерабатывается (а также почему несколько веб-серверов не могут делить состояние сеанса InProc.

Этот вновь созданный и завышенный объект застревает в коллекции Context.Items, чтобы он был доступен в течение всего времени запроса.

Любые изменения, внесенные в объект Session, затем сохраняются в конце запроса в хранилище сеансов путем сериализации (или обновляется запись InProc, HttpCache).

Так как Session_End срабатывает без текущего запроса "на лету", объект Session разворачивается ex-nilo, без информации. Если вы используете сеанс InProc, истечение срока действия HttpCache запускает событие обратного вызова в ваше событие Session_End, поэтому запись сеанса доступна, но по-прежнему является копией того, что было в последний раз сохранено в HttpContext.Cache. Это значение сохраняется в свойстве HttpApplication.Session с помощью внутреннего метода (называемого ProcessSpecialRequest), где он доступен. Во всех других случаях он внутренне исходит из значения HttpContext.Current.Session.

Ваш ответ

Поскольку Session_End всегда срабатывает против нулевого Контекста, вы должны ВСЕГДА использовать это. Сессия в этом событии и передача объекта HttpSessionState до вашего кода отслеживания. Во всех других контекстах он отлично подходит для извлечения из HttpContext.Current.Session, а затем переходит в код трассировки. НЕ, тем не менее, пусть трассировочный код достигнет контекста сеанса.

Мой ответ

Не используйте Session_End, если вы не знаете, что используемое хранилище сеансов поддерживает Session_End, которое делает, если оно возвращает true из SetItemExpireCallback. Единственное хранилище в коробке, которое делает это хранилище InProcSessionState. Можно написать хранилище сеансов, но вопрос о том, кто будет обрабатывать Session_End, является неоднозначным, если есть несколько серверов.

Ответ 3

Думаю, вы уже ответили на свой вопрос: обычно свойство Session в Global.asax и HttpContext.Current.Session одинаковы (если есть текущий запрос). Но в случае таймаута сеанса активный запрос отсутствует, поэтому вы не можете использовать HttpContext.Current.

Если вы хотите получить доступ к сеансу из метода, называемого Session_End, передайте его как параметр. Создайте перегруженную версию метода Log(), который принимает параметр HttpSessionState в качестве параметра, затем вызовите Tracker.Log(this.Session) из обработчика событий Session_End.

Кстати: вы знаете, что в любом случае вы не можете полагаться на событие конца сеанса? Он будет работать только до тех пор, пока у вас есть состояние сеанса в процессе. При использовании SQL-сервера или StateServer для управления состоянием сеанса событие завершения сеанса не будет срабатывать.

Ответ 4

Событие Session_End возникает только тогда, когда sessionstate mode установлен в InProc в файле Web.config. Если для режима сеанса установлено значение StateServer или SQLServer, событие не будет поднято.

используйте Session["SessionItemKey"], чтобы получить значение сеанса.

Ответ 5

Хорошо, у меня есть одна и та же проблема, чтобы отслеживать активность сеанса. Вместо использования события session_end я реализовал интерфейс IDisposable и деструктор для своего класса sessiontracker. Я изменил метод Dispose(), чтобы сохранить активность сеанса в DB. Я вызвал метод obj.Dispose(), когда пользователь нажимает кнопку выхода из системы. Если пользователь по ошибке закрыл браузер, тогда GC будет вызывать деструктор при очистке объектов (не сразу, но наверняка он вызовет этот метод через некоторое время). Метод деструктора внутренне выполняет один и тот же метод Dispose() для сохранения активности сеанса в БД.

-Shan

Ответ 6

Сессия доступна в вашем файле Global.asax во время события Session_Start. Может быть, подожди, пока этот момент не начнется?

Ответ 7

Помните, что Session_End запускается, когда сеанс отключается без активности. Браузер не инициирует это событие (потому что он неактивен), поэтому единственный раз, когда вы на самом деле получите событие, является использование провайдера InProc. В любом другом провайдере это событие никогда не срабатывает.

Мораль? Не используйте Session_End.