На этот раз, как и обещал в статье “Сессия ASP.NET и проблема с параллельными запросами”, я расскажу, как можно отладить обращения к сессии с помощью нестандартного провайдера.
А в завершение, в качестве бонуса, расскажу забавные подробности про режим ReadOnly, который иногда не совсем ReadOnly…
Для чего может потребоваться логгирование обращений к сессии? На мой взгляд, наиболее подходящими являются два сценария:
- “Нужно ускорить некоторые страницы нашего приложения, давайте посмотрим где используется сессия и нужна ли там запись в сессию или сессия вообще.”
- “Для того, чтобы не блокировать конкурентные запросы в рамках одной сессии, давайте прогоним все наши сценарии и проанализируем обращения к сессии.”
Альтернативы
Первая альтернатива, про которую я подумал – IntelliTrace. Проверка показала, что встроенных средств у неё нет. Можно, в принципе, написать своё расширение, но настройка его для конечного пользователя, скажем так, не максимально простая… Еще один неприятный момент – IntelliTrace доступен только в редакции Ultimate.
Вторая альтернатива – ASP.NET Application Tracing. По факту оказалось, что помощи от этого варианта немного, но зато его можно использовать для вывода информации (об этом позже).
Идея
Когда я сам задался вопросом, как можно отследить обращения к сессии, первой мыслью было – “может есть какой-нибудь интерфейс, который можно реализовать и подсунуть провайдеру сессии?”. Следующая мысль была – “снимай розовые очки – это тебе не MVC, это старый добрый ASP.NET” :)
Что ж, если нет такого интерфейса, значит нужно его написать. Хорошая новость в том, что можно реализовать свой провайдер сессии. Плохая новость – реализация занимает несколько больше 5 минут. Именно по этой причине я решил выложить исходники на bitbucket (лицензия хорошая – MIT).
Тем, кому не особо интересна концепция и её реализация, могут просто дальше не читать – чтобы начать пользоваться моей библиотекой достаточно перейти на страницу проекта в bitbucket, скачать исходники и посмотреть пример. По крайней мере, я надеюсь, что всё получилось достаточно простым.
А тем кому интересно, я расскажу про основные шаги реализации проекта, от точки входа для тех кто будет расширять логгер, до некоторых деталей реализации своего провайдера.
Интерфейс логгера
Я не стал выдумывать что-то невероятно гибкое и универсальное, а сформулировал простую задачу: должен быть один метод, который записал бы в лог факт вызова определённого метода и значения его параметров.
Сразу скажу, что полные примеры кода приводить не буду, только небольшие иллюстрации. Итак, встречайте – ISessionLogger:
public interface ISessionLogger
{
void LogMethod(object instance, string methodName, params string[] parameters);
}
Простой как пятак, но больше, на мой взгляд, и не нужно.
Базовый логгер
Сначала у меня была мысль по умолчанию записывать лог с помощью log4net (исторически сложилось, что в ФогСофт мы используем именно его). Однако потом я решил, что навязывание своих предпочтений не самая правильная вещь для open-source проекта.
По этой причине базовая реализация просто пишет в Trace (который в System.Diagnostics). Реализация LogMethod выглядит так:
LogMessage(GetMessage(instance, methodName, parameters));
Здесь LogMessage виртуальный метод, который в базовом классе пишет строку в Trace, а в классах-наследниках – всё что вам будет угодно :) GetMessage я тоже сделал виртуальным – вдруг кому-то и это захочется переопределить.
Городить огород с блэкджеком красивыми паттернами я не стал – логгер создаётся по названию типа из web.config. Если его нет или он не найден – создаётся базовый логгер. Кстати, так выглядит конфигурация в web.config, которой достаточно для логгирования обращений к сессии:
<sessionState customProvider="ProfilingSessionStateStoreProvider" mode="Custom">
<providers>
<clear />
<add name="ProfilingSessionStateStoreProvider"
type="OlegAxenow.AspNetSessionProfiler.ProfilingSessionStateStoreProvider"
logProvider="OlegAxenow.AspNetSessionProfiler.TraceSessionLogger" />
</providers>
</sessionState>
Расширение базового логгера
В качестве примера я сделал вариант с выводом информации в HttpContext.Trace, при этом класс содержит всего один небольшой метод:
protected override void LogMessage(string message)
{
if (HttpContext.Current == null)
base.LogMessage(message);
else
HttpContext.Current.Trace.Write(GetType().Name, message);
}
Теперь можно подключить в web.config другой класс и наслаждаться выводом информации на страницу “/Trace.axd”, не забыв включить поддержку там же в web.config:
<trace enabled="true" pageOutput="false" requestLimit="40" localOnly="false" />
Упрощённая реализация провайдера
Как подключить свой провайдер я уже написал выше, теперь расскажу как его написать. Сразу стоит отметить, что, по причине отсутствия цели написать код для работы в промышленной эксплуатации, провайдер как и хранилище сессии можно написать очень по-простому. Далее перечислю основные шаги (посмотреть на их реализацию можно в классе ProfilingSessionStateStoreProvider):
- Наследуем класс от абстрактного класса SessionStateStoreProviderBase и создаем все необходимые методы.
- Реализуем метод Initialize (читаем конфигурацию, сохраняем тип логгера).
- Делаем несколько рутинных действий по реализации стандартных методов. Особый интерес из них представляет только следующий.
- Метод CreateNewStoreData вызывается для создания хранилища данных сессии и именно в нём происходит создание (c помощью Activator.CreateInstance) и подключение логгера.
- Также в этом методе создается своя коллекция для элементов сессии (ProfilingSessionStateItemCollection), которой логгер передаётся как параметр.
На самом деле всё чуть-чуть сложнее, но не настолько, чтобы заслужить упоминания. Теперь у вас достаточно вводной информации чтобы использовать и расширять функционал по логгированию обращений к сессии.
ReadOnly?
А теперь обещанный бонус. Режим ReadOnly никак не регламентирует поведение при попытке установки значения в сессию – он относится только к блокировкам параллельных обращений в рамках одной сессии. Другими словами – этот режим только говорит о намерении ничего не записывать в сессию. Можно добавить на такую страницу код записи значения в сессию, и все будет нормально работать (в режиме сессии InProc).
Итак, вот что мы знаем про режим ReadOnly:
- Он не блокирует другие страницы.
- Можно записывать в сессию даже в этом режиме (exception не будет).
Однако я не рекомендую пользоваться последним наблюдением. Причина в непредсказуемости поведения, особенно для разных реализаций сессии. Например, в варианте StateServer вы с большой вероятностью получите null при попытке считать значение, записанное в сессию обработчиком другого запроса.
Резюме
Если вам интересен предложенный вариант, пользуйтесь моим проектом на здоровье. Если вдруг встретите баги или появятся предложения по доработке – пишите в комментариях, а лучше сразу на bitbucket.
Что касается планов для дальнейших статей – пока до конца не определился, вероятно напишу про опыт использования “Visual Studio Productivity Power Tools”.
Если у вас есть замечания, пожелания или новые темы – пишите в комментариях,твиттер или на olegaxenow.reformal.ru. Постараюсь учесть.
"снимай розовые очки – это тебе не MVC, это старый добрый ASP.NET"
ОтветитьУдалитьЭто не корректное сравнение, так как кагбы MVC тоже строится на Asp.Net-е, и провайдеры, как и проблемы у них общие.
Корректнее сравнивать MVC с WebForm-ами.
To @Sergey Litvinov:
ОтветитьУдалитьТут мы просто друг друга не поняли - под "старым добрым ASP.NET" я понимаю именно Web Forms. ASP.NET MVC еще молод, IMHO :)