воскресенье, 18 декабря 2011 г.

Отладка сессии ASP.NET

CrashTestDummyНа этот раз, как и обещал в статье “Сессия 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):

  1. Наследуем класс от абстрактного класса SessionStateStoreProviderBase и создаем все необходимые методы.
  2. Реализуем метод Initialize (читаем конфигурацию, сохраняем тип логгера).
  3. Делаем несколько рутинных действий по реализации стандартных методов. Особый интерес из них представляет только следующий.
  4. Метод CreateNewStoreData вызывается для создания хранилища данных сессии и именно в нём происходит создание (c помощью Activator.CreateInstance) и подключение логгера.
  5. Также в этом методе создается своя коллекция для элементов сессии (ProfilingSessionStateItemCollection), которой логгер передаётся как параметр.

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

ReadOnly?

А теперь обещанный бонус. Режим ReadOnly никак не регламентирует поведение при попытке установки значения в сессию – он относится только к блокировкам параллельных обращений в рамках одной сессии. Другими словами – этот режим только говорит о намерении ничего не записывать в сессию. Можно добавить на такую страницу код записи значения в сессию, и все будет нормально работать (в режиме сессии InProc).

Итак, вот что мы знаем про режим ReadOnly:

  • Он не блокирует другие страницы.
  • Можно записывать в сессию даже в этом режиме (exception не будет).

Однако я не рекомендую пользоваться последним наблюдением. Причина в непредсказуемости поведения, особенно для разных реализаций сессии. Например, в варианте StateServer вы с большой вероятностью получите null при попытке считать значение, записанное в сессию обработчиком другого запроса.

Резюме

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

Что касается планов для дальнейших статей – пока до конца не определился, вероятно напишу про опыт использования “Visual Studio Productivity Power Tools”.

Если у вас есть замечания, пожелания или новые темы – пишите в комментариях,твиттер или на olegaxenow.reformal.ru. Постараюсь учесть.

2 комментария:

  1. "снимай розовые очки – это тебе не MVC, это старый добрый ASP.NET"
    Это не корректное сравнение, так как кагбы MVC тоже строится на Asp.Net-е, и провайдеры, как и проблемы у них общие.
    Корректнее сравнивать MVC с WebForm-ами.

    ОтветитьУдалить
  2. To @Sergey Litvinov:

    Тут мы просто друг друга не поняли - под "старым добрым ASP.NET" я понимаю именно Web Forms. ASP.NET MVC еще молод, IMHO :)

    ОтветитьУдалить