Мировые информационные ресурсы

Анатомия приложений ASP.NET

Показывать лекцию целиком

Модель компиляции ASP.NET

Как нам стало известно ранее, платформа .NET Framework имеет особую, двухэтапную модель компиляции приложений. Эта модель подразумевает получения промежуточного кода на языке MSIL, который впоследствии преобразуется в машинный код.

Поскольку приложения ASP.NET работают в рамках общей среды исполнения платформы .NET Framework, они также работают на основе этого механизма. Несмотря на это, платформа ASP.NET имеет свои особенности компиляции приложений.

Вообще, если рассматривать способы исполнения веб-приложений, то можно выделить два направления:

  • интерпретируемые приложения;
  • компилируемые приложения.
  • Интерпретируемые приложения исполняют исходный текст программы построчно. Интерпретатор в этом случае считывает очередную строку из текста программы и исполняет её, после чего переходит к следующей строке. Общий алгоритм подобного подхода можно описать следующей схемой.

    К основным недостаткам подобного подхода относится невысокая скорость исполнения программного кода. Также, при подобном подходе нет возможности проверить весь листинг кода на наличие ошибок. Например, если исходный код программы состоит из 100 строк и в строке 50 содержится ошибка, то первые 49 строк будут успешно исполнены, а на строке 50 будет выдана ошибка. Такое поведение не всегда желательно, поэтому данный способ мало популярен в современной разработке приложений.

    Платформа ASP.NET использует альтернативный подход к исполнению приложений, а именно компиляцию исходных файлов в бинарный исполняемый файл перед выполнением. В этом случае перед запуском приложения исходный файл преобразуется в исполняемый файл. Это позволяет отследить часть ошибок на этапе компиляции (например, синтаксические ошибки). Кроме того, в момент исполнения приложения не требуется выполнять проверку и разбор синтаксических конструкций языка, что увеличивает производительность таких приложений относительно интерпретируемых приложений.

    Схема исполнения компилируемых приложений выглядит следующим образом.

    Как мы увидим в последующих лекциях (когда будем подробно разбирать механизмы работы страниц ASP.NET), каждая страница состоит из разметки (HTML и ASPX кода) и программного кода. Особенностью платформы ASP.NET является то, что все элементы веб-приложения (страницы, элементы управления, обработчики и т.д.) компилируются, а не интерпретируются.

    Однако, если с компиляцией программного кода на языке C# (который является частью страницы ASP.NET) все понятно, то каким же образом компилируется разметка страницы? Ведь разметка страницы, по сути, представляет собой обычный текст на языке HTML, который должен обрабатываться бразуером, но для сервера он является обычной последовательностью символов. Давайте разберемся в этом вопросе более подробно.

    Если рассмотреть страницу ASP.NET с точки зрения объектной модели, то каждая страница является классом, который в свою очередь является наследником базового класса Page. С другой стороны в структуре проекта страница ASP.NET представляется двумя файлами – файлом разметки (.aspx) и файлом с программным кодом (.cs или .vb).

    Если более внимательно рассмотреть файл содержащий программный код для страницы, то в нем можно заметить одну особенность – класс страницы (наследник класса Page) при определении содержит ключевое слово partial.

    Ключевое слово partial при определении класса говорит компилятору о том, что описание данного класса может содержатся в нескольких файлах. Действительно, при компиляции страницы ASP.NET на основе файла разметки создается вторая часть этого класса. При компиляции такого класса он собирается в одно целое и компилируется в язык MSIL.

    Схема процесса компиляции страницы ASP.NET в программный код может выглядеть следующим образом.

    Каким образом разметка преобразуется в программный код? В общем случае разметка трансформируется в вызовы методов "Response.Write()", которые записывают информацию в выходной буфер, который пересылается клиенту. Таким образом, получается полностью скомпилированная страница, которая может запускаться на исполнение.

    Для ускорения работы все скомпилированные страницы хранятся в специальной системной папке "C:\Windows\Microsoft.NET\[version]\Temporary ASP.NET Files\" (где [verison] – версия .NET Framework). Эта папка имеет следующую структуру.

    Для каждого сайта создается своя папка, в которой присутствуют скомпилированные страницы. В каждой такой папке содержаться временные файлы и информация об исходной странице. Эта информация необходима для отслеживания изменения исходной страницы.

    Поскольку существует возможность размещения на веб-сервере проекта в виде исходных файлов, то зачастую компиляция страниц ASP.NET производится в момент первого обращения к этой странице по протоколу HTTP. Из-за этого при первом обращении к страницам веб-приложения могут быть небольшие задержки. Для того, чтобы этого избежать можно выполнить предкомпиляцию приложения.

    Предкомпиляция приложения – это процесс предварительной компиляции всех страниц веб-приложения с целью получения исполняемых сборок. Предкомпиляция веб-приложения может выполняться с целью исключения ситуации с задержкой ответа при первом обращении к странице, а также распространение веб-приложения в виде бинарных исполняемых сборок, а не исходных кодов.

    Существует два варианта предкомпиляции веб-приложений:

    предкомпиляция приложения с возможностью последующего обновления в этом случае исходный код разметки страниц (.aspx) остается в неизменном виде и при изменении этой страницы на сервере она перекомпилируется при первом обращении;
    предкомпиляция приложения без возможности последующего обновления в этом случае все файлы компилируются в бинарные сборки и изменение страниц на веб-сервере невозможно.

    Предкомпиляцию приложения можно выполнить двумя способами – с помощью Visual Studio и отдельной утилитой aspnet_compiler.

    Для предкомпиляции приложения средствами Visual Studio необходимо выбрать пункт меню "Publish Web Site" и в появившемся окне задать адрес публикации и необходимые параметры.

    Утилита aspnet_compiler поставляется в составе .NET Framework и может быть использована в случае, если Visual Studio в данный момент недоступна (например, требуется выполнить предкомпиляцию приложения прямо на сервере) или требуется автоматизировать процесс предкомпиляции.

    Другой важной особенностью компиляции приложений ASP.NET является возможность разграничения области исполнения различных приложений. Поскольку в рамках одного сервера одновременно может исполняться несколько приложений, то разграничение работы приложений является важным моментом. Такое разграничение производится за счет доменов приложений.

    Домен приложения – это .NET-аналог процесса в операционной системе. Основной задачей доменов приложения является разграничение зоны действия для различных веб-приложений. Такое разграничение необходимо для обеспечения безопасности. Например, при таком подходе невозможен сценарий, при котором некачественный код одного приложения выведет из строя другое веб-приложение.

    Для каждого отдельного каталога на сервере создается отдельный домен приложения. Использование такой модели позволяет разграничить память, выделяемую каждому приложению. Кроме того, в рамках одного домена приложения совместно используются одни и те же ресурсы, находящиеся в памяти – глобальные данные приложения (Application), данные сеансов пользователей (Session), кэшированные данные (Cache) и др. Эта информация не является доступной напрямую другим приложениям, работающим за рамками данного домена приложения. Кроме того, все страницы ASP.NET и другие ресурсы совместно используют одни и те же конфигурационные настройки, которые наследуются из корневого файла web.config.

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

    Время жизни приложения может зависеть от различных факторов. Домен приложения может быть закрыт в силу различных причин. Основные причины прекращения работы домена приложения:

  • домен приложения закрывается при остановке или перезапуске web-сервера;
  • домен приложения закрывается при длительном отсутствии активности в отношении данного приложения;
  • домен приложения может закрываться при возникновении необработанной глобальной ошибки, которую не удалось отловить;
  • домен приложения закрывается при обновлении ресурсов в рамках данного приложения;
  • домен приложения можно закрыть принудительно, вызвав статический метод HttpRuntime.UnloadAppDomain().
  • Закрытие домена приложения при отсутствии активности обусловлено оптимизацией использования ресурсов операционной системы. При закрытии домена приложения освобождаются ресурсы, занимаемые этим доменом, например оперативная память.

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

    В момент исполнения программного кода той или иной сборки, среда исполнения CLR блокирует эту сборку на время исполнения кода. По этой причине ASP.NET не размещает исполняемые файлы в папке с приложением, а вместо этого во время компиляции использует теневое копирование. Для этого в момент компиляции все исполняемые файлы помещаются в папку "C:\Windows\Microsoft.NET\[version]\Temporary ASP.NET Files\". Очень важно, что наряду с бинарными исполняемыми файлами в этой папке хранятся XML-описания сущностей ASP.NET. Это позволяет обнаружить изменения в исходных файлах и, в случае необходимости, перекомпилировать их.

    Краткие итоги

    Компиляция страниц ASP.NET имеет свою специфику по сравнению с компиляцией остальных типов приложений. Все сущности ASP.NET (страницы, элементы управления и т.д.) компилируются в бинарные исполняемые сборки. При этом существует специальная системная папка, в которой расположены все исполняемые сборки. Компиляция страницы ASP.NET в исполняемую сборку происходит в момент первого обращения к этой странице. Из-за этого могут возникать небольшие задержки генерации страницы в момент первого обращения. Для того чтобы этого избежать существует механизм предкомпиляции приложений. Важную роль в компиляции и обновлении приложений ASP.NET играют домены приложения – они позволяют разграничить исполнение различных приложений, которые расположены на одном сервере.

    Глобальные события приложения

    Процесс обработки запроса любого веб-приложения нетривиален. Перед тем, как будет запущен программный код генерации результата, будет пройдено еще несколько этапов. Среди этих этапов – подготовка к обработке запроса, проверка системы безопасности, проверка наличия страницы в кэше, восстановление состояния и др. После генерации результата и перед отправкой результата клиенту также выполняется набор действий – сохранение страницы в кэше (если это необходимо), сохранение состояния сеанса и др. В некоторых ситуациях в разрабатываемых приложениях требуется отследить момент выполнения того или иного действия. Для этого была создана модель глобальных событий ASP.NET.

    Модель событий ASP.NET содержит два типа глобальных событий, которые можно обработать: события, которые возникают каждый раз при обработке запроса и события, которые возникают не всегда, а только при определенных условиях.

    События, которые возникают при обработке каждого запроса генерируются в следующем порядке:

    BeginRequest генерируется в начале выполнения каждого запроса;
    AuthenticateRequest генерируется непосредственно перед моментом как будет выполнена аутентификация. Это стартовая точка для создания собственной системы аутентификации;
    AuthorizeRequest генерируется непосредственно перед моментом авторизации;
    ResolveRequestCache генерируется в момент проверки локального кэша на наличие текущей страницы в кэше;
    AcquireRequestState генерируется в момент, когда для клиента будет получена информация, специфичная для сеанса (Session);
    PreRequestHandlerExecute генерируется непосредственно перед тем, как управление будет передано HTTP-обработчику;
    PostRequestHandlerExecute генерируется сразу после того, как запрос будет обработан HTTP-обработчиком;
    ReleaseRequestState генерируется в момент, когда информация, специфичная для сеанса сериализуется из коллекции Session в строку, чтобы стать доступной для следующего запроса;
    UpdateRequestCache генерируется в момент добавления информации в кэш выходных данных;
    EndRequest генерируется в конце запроса перед тем, как объекты будут уничтожены.

    Также есть ряд специфичных событий, которые генерируются при определенных условиях:

    Start генерируется в момент, когда впервые запускается приложение и создается домен приложения;
    SessionStart генерируется всякий раз, когда начинается новый сеанс;
    SessionEnd генерируется при завершении сеанса;
    Error генерируется всякий раз, когда в приложении возникает необработанное событие;
    End генерируется в момент завершения работы приложения;
    Disposed генерируется после завершения работы приложения, когда сборщик мусора .NET готов к восстановлению занимаемой памяти.

    Для использования модели событий ASP.NET необходимо переопределить класс приложения HttpApplication. Для этого нужно добавить в проект файл приложения global.asax.

    Для того чтобы подписаться на глобальные события, необходимо в файл приложения добавить методы, имена которых соответствуют именам событий, причем к имени события необходимо добавить название "Application_". Например, чтобы подписаться на событие Start, необходимо создать метод "Application_Start". Аналогичным образом можно создать обработчики и для других событий. Пример файла приложения с созданными обработчиками глобальных событий приведен ниже.

    Аналогичным образом на глобальные события можно подписаться из другого места, например, HTTP-модуля ASP.NET (этот механизм будет рассмотрен далее). Для этого следует воспользоваться классом HttpApplication.

    Глобальные события нужны далеко не в каждом приложении ASP.NET. Если приложение выполняет простые операции, использующие готовые алгоритмы, то глобальные события могут не понадобиться. Однако, в более сложных ситуациях использование глобальных событий ASP.NET является очень удобным. Например, при разработке собственной системы безопасности разработчику не требуется думать о том, в какой момент лучше проверять права доступа у данного пользователя – он может успешно подписаться на соответствующее событие, а среда исполнения ASP.NET вовремя сгенерирует нужное событие. Аналогично, если требуется улучшить систему кэширования, то можно подписаться на нужные события и встраивать свою логику кэширования в существующую инфраструктуру.

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

    Краткие итоги

    При обработке HTTP-запроса веб-приложение выполняет ряд действий, скрытых от разработчика приложений. Для того чтобы вмешаться в обработку этого процесса ASP.NET имеет модель событий. Разработчик может подписаться на любое событие модели событий ASP.NET. Это можно сделать, используя глобальный файл приложения global.asax или использовать класс HttpApplication.

    Служебные объекты ASP.NET

    В некоторых ситуациях в процессе работы приложения требуется получать специфические данные о контексте исполнения. Например, необходимо работать с данными HTTP-запроса и HTTP-ответа, получать серверную информацию или оставлять отладочные данные. Обычно в других платформах для построения веб-приложений использовались переменные окружения. Для работы с этими данными в ASP.NET существует ряд служебных объектов ASP.NET, позволяющих выполнять указанные операции более удобно и безопасно.

    Основными служебными объектами ASP.NET являются:

  • HttpContext;
  • Request;
  • Response;
  • Server;
  • Trace.
  • Объект HttpContext является "входной точкой" для получения доступа ко всем остальным объектам. Фактически, через этот объект можно получить всю информацию о текущем контексте исполнения. Обычно объект HttpContext используется за пределами страницы, т.к. страница также позволяет получить доступ к этим объектам. Для того, чтобы получить доступ к текущему контексту достаточно обратиться к статическому свойству Current класса HttpContext: "HttpContext.Current". При получении доступа к текущему контексту становятся доступны все остальные объекты с одноименными названиями свойств.

    Объекты Request и Response отвечают за содержимое HTTP-запроса и HTTP-ответа.

    Объект Request содержит все параметры URL, HTTP-заголовки и другую информацию, отправляемую клиентом. Кроме того, в этом объекте инкапсулируется информация о браузере клиента.

    Объект Response описывает HTTP-ответ, который впоследствии отправляется клиенту. Используя объект Response можно модифицировать HTTP-ответ – выставлять коды возврата, перенаправлять пользователя на другую страницу, задавать заголовки HTTP-ответа или просто формировать содержимое, которое будет отправлено клиенту. Для помещения данных в выходной буфер используется метод Write класса Response. Таким способом пользуются при разработке собственных элементов управления или HTTP-обработчиков.

    Например, давайте создадим фрагмент кода, который считывает адрес, по которому обратился пользователь и количество HTTP-заголовков в HTTP-запросе и сохраним их в HTTP-заголовках HTTP-ответа. Для этого у объектов Request и Response существует коллекция Headers.

    Этот пример показывает, каким образом можно работать с объектами Request и Response.

    Объект Server предоставляет вспомогательные методы и свойства, необходимые для функционирования веб-приложения. Основные методы и свойства объекта Server:

    MachineName позволяет узнать имя компьютера;
    HtmlEncode() и HtmlDecode() заменяет обычную строку строкой допустимых символов HTML, и наоборот;
    UrlEncode() и UrlDecode() заменяет обычную строку строкой допустимых символов URL, и наоборот;
    MapPath() преобразует относительный путь к файлу в рамках веб-приложения в абсолютный путь к файлу на диске (к примеру из "~/images/pic1.png" получается физический путь, например "C:\inetpub\wwwroot\images\pic1.png");
    Transfer() передает исполнение другой веб-странице в текущем приложении.

    Наконец объект Trace является универсальным объектом трассировки. Он позволяет записывать отладочную информацию в отладочный журнал на уровне страниц. Для отображения отладочной информации на уровне страницы необходимо задать значение атрибута Trace в директиве страницы равным значению True. Для этого необходимо открыть шаблон страницы (.aspx) и сделать необходимые изменения.

    После выполнения этой операции при выполнении каждого запроса к этой странице после содержимого самой страницы будет отображаться отладочная информация. Пример отображения отладочной информации представлен ниже.

    В составе этой отладочной информации содержится детальная информация о запросе, информация о трассировке, дерево элементов управления на странице, состояние сеанса (Session), состояние приложения (Application), содержание коллекции Cookie, HTTP-заголовки и другая полезная информация.

    Также видно, что в рамках этой информации содержится раздел о выполняющихся событиях. Он позволяет отследить последовательность выполнения событий в рамках обработки запроса. Если нужно добавить свое событие в этот список, то можно воспользоваться объектом Trace. Для этих целей этот объект содержит два метода – Write и Warn. Оба этих метода добавят запись о своем выполнении. Отличие их состоит в том, что метод Write сделать обычную запись о выполнении события, а метод Warn также выделит эту запись красным шрифтом.

    Например, можно написать следующий фрагмент кода, который добавляет информацию в список трассировки в момент создания объекта страницы и в момент обработки события Page Load.

    Запустив страницу на исполнение при включенной трассировке можно увидеть следующий набор сообщений.

    Таким образом, служебные объекты ASP.NET необходимы для получения и изменения служебной информации о текущем контексте исполнения.

    Краткие итоги

    В рамках среды исполнения ASP.NET существует ряд служебных объектов, которые позволяют получить доступ к различной служебной информации. К такой информации относятся данные об HTTP-запросе и HTTP-ответе, информация о сервере, трассировке и др. Доступ ко всем объектам можно получить либо через одноименные свойства страницы, либо через служебный объект HttpContext.

    Зарезервированные папки

    При разработке веб-приложения иногда требуется разместить специальные служебные файлы, которые не должны быть доступны для скачивания. Например, к таким файлам относятся файлы данных СУБД, файлы тем, файлы с программным кодом, файлы ресурсов и др. Если разместить подобные файлы за пределами папки с веб-приложением, то потребуется произвести ряд настроек для обеспечения прав доступа к этим папкам. Более того, такой подход не очень удобен с точки зрения администрирования такого приложения. Гораздо удобнее – размещать все необходимые файлы в папке с самим приложением. Однако, если пользователь будет иметь возможность скачать эти файлы по протоколу HTTP – это несомненная уязвимость в безопасности веб-приложения.

    Для таких случаев можно создать специальные служебные папки в составе веб-приложения и запретить к ним доступ по протоколу HTTP. Это можно сделать, используя стандартные средства безопасности ASP.NET. Однако по неосторожности некоторые разработчики могут забыть это сделать, и это сделает приложение уязвимым (например, если пользователи скачают файл данных, потенциально они могут получить доступ к паролям всех пользователей веб-приложения). Поэтому в структуре проекта приложения ASP.NET существует ряд стандартных зарезервированных папок.

    Зарезервированными папками являются обычные папки в составе проекта ASP.NET, которые, однако, имеют заранее предопределенные имена и содержат строго специфичную информацию. Основным отличием зарезервированных папок от остальных является невозможность загрузить их содержимое напрямую по протоколу HTTP.

    К зарезервированным папкам относятся следующие папки:

    Bin содержит все предварительно скомпилированные сборки .NET, используемые web-приложением ASP.NET;
    App_Code содержит файлы исходного кода, которые не соотносятся с конкретными страницами;
    App_GlobalResources содержит файлы глобальных ресурсов, которые доступны всем страницам web-приложения;
    App_LocalResources содержит файлы локальных ресурсов, которые специфичны для каждой страницы web-приложения;
    App_Data содержит файлы данных. Например, файлы SQL Server, текстовые файлы, XML и проч.;
    App_Browsers содержит определения браузеров, которые определяет разработчик конкретного web-приложения;
    App_Themes содержит файлы тем для оформления сайта

    Для того, чтобы создать зарезервированную папку в составе проекта ASP.NET следует выбрать пункт меню "Add ASP.NET Folder" во всплывающем меню приложения.

    После создания зарезервированной папки в структуре проекта ASP.NET, не требуется выполнять никаких дополнительных действий по ограничению доступа к этим папкам.

    Краткие итоги

    В структуре проекта ASP.NET можно создать ряд служебных папок, в которых будет содержаться информация служебного характера. К такой информации относятся файлы данных, программного кода, ресурсы, темы и др. Основным отличием зарезервированных папок является то, что их содержимое не доступно напрямую по протоколу HTTP.

    Конфигурация приложений

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

    Поэтому очень часто такие значения выносят за рамки исходного кода программы и сохраняют их во внешнем файле, который впоследствии можно изменить – файл конфигурации.

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

    В более ранних инструментах разработки приложений не содержалось встроенных средств для работы с настройками приложения, поэтому разработчикам каждый раз приходилось этот механизм разрабатывать заново. Формат настроек не был стандартизирован и каждый разработчик хранил в том формате, в котором считал нужным (например, в текстовом файле или в бинарном файле с собственной структурой). Подобный подход создавал ряд неудобств при разработке и использовании приложений. При разработке приложений приходилось постоянно возвращаться к вопросу о том, как хранить настройки приложения. При использовании приложения никогда не было стандартизированного формата конфигурационного файла, который можно использовать.

    Платформа .NET Framework решает этот вопрос – в рамках платформы существует стандартный способ хранения конфигурации. В качестве формата для хранения настроек используется формат XML. Использование XML в качестве формата файла конфигурации обладает рядом преимуществ:

  • он имеет иерархическую структуру, что, несомненно, удобно для хранения настроек приложения;
  • файл XML, по сути, является текстовым файлом, что означает, что настраивать приложения .NET можно используя любой текстовой редактор.
  • Все конфигурационные файлы в рамках платформы .NET имеют расширение ".config". Например, для настройки веб-приложений используются файлы "web.config", а для настольных – "app.config".

    Существует целая иерархия файлов конфигурации. Схема хранения файлов настроек показана ниже.

    Система конфигурации .NET Framework разделена на две части – глобальные настройки и настройки приложения. Настройки, заданные в глобальных файлах конфигурации доступны для каждого приложения, т.е. глобальные настройки наследуются в каждом приложении. На уровне приложения определяется главный файл конфигурации (он расположен в корневой папке приложения), в котором задаются основные настройки приложения. Для каждой подпапки в рамках проекта может быть определен собственный файл конфигурации. В этом случае для этой подпапки существует собственная конфигурация, которая наследуется из основного файла конфигурации и уточняется файлом конфигурации в данной папке. Такая иерархия позволяет определить глобальные настройки и постепенно, где это необходимо, уточнять их.

    Глобальные файлы настроек расположены в папке "C:\Windows\Microsoft.NET\Framework\*версия+\CONFIG". Как видно из приведенной выше схемы в этой папке содержаться два файла – machine.config и web.config. Оба этих файла содержат конфигурационные настройки и имеют идентичный формат. Разница этих файлов заключается в том, что параметры, определенные в файле "machine.config" нельзя переопределить на уровне приложения, а параметры, определенные в файле "web.config" можно переопределять на любом уровне иерархии. Например, давайте рассмотрим параметр, который отвечает за конкурентный доступ к веб-сервисам на основе платформы WCF. Если этот параметр определить в глобальном файле "web.config", то он будет доступен для каждого приложения. При этом нет необходимости определять его для каждого приложения – он уже определен. Однако, если требуется изменить этот параметр для конкретного приложения, то это можно легко сделать переопределив этот параметр в файле "web.config" для приложения. Однако, если переместить определение этого параметра из глобального файла "web.config" в глобальный файл "machine.config", то переопределить параметр на уровне приложения уже будет нельзя.

    Возможность такого "жесткого" задания конфигурационных параметров позволяет явным образом задавать политики сервера, в рамках которых исполняются множество приложений. При этом каждое приложение не сможет установить настройки сервера так, как ему это необходимо.

    Как уже было сказано ранее, конфигурационные файлы построены на базе формата XML. Пример конфигурационного файла приведен ниже.

    Как видно, файл конфигурации содержит ряд настроек в иерархическом виде. Каждый узел в файле настроек определяет отдельный аспект функционирования веб-приложения – настройки кеширования, настройки компиляции и отладки, строки соединения с СУБД, параметры безопасности и т.д. Для каждого узла определен собственный класс-обработчик этих настроек. Узел не может быть добавлен в конфигурационный файл, если с ним не ассоциирован никакой класс-обработчик. В противном случае при запуске приложения будет сгенерирована ошибка.

    Система конфигурации .NET Framework предполагает использование файлов конфигурации и для хранения собственных параметров конфигурации приложения, а не только общих настроек. Для того, чтобы добавить свою секцию конфигурации в файл настроек, для начала следует определить эту секцию специальным образом. Для этого существует стандартная секция "configSections". Определить секцию конфигурации можно следующим образом.

    В приведенном примере мы определяем секцию с именем "MySection". При этом должен быть создан класс "MyConfigSection", который является обработчиком параметров этой секции. После в конфигурационном файле можно определить и саму конфигурационную секцию и задать нужные параметры.

    Количество параметров, уровень вложенности и весь внешний вид секции конфигурации определяет класс-обработчик для этой конфигурационной секции. Этот класс является наследником базового класса "ConfigurationSection", который содержит базовые механизмы для работы с данными конфигурационных файлов. В наиболее простой ситуации можно определить обработку свойств секции, в данном случае свойства "MyParam1". Для этого необходимо создать в классе-обработчике новое публичное свойство и разметить его соответствующим атрибутом "ConfigurationProperty".

    Такое определение позволит среде исполнения .NET Framework корректно интерпретировать конфигурационную секцию.

    Следует отметить, что все конфигурационные секции, доступные по умолчанию в .NET Framework объявлены подобным образом (используя секцию "configSections"). Эти определения сделаны в глобальном файле "machine.config".

    Наконец, для получения доступа к существующим конфигурационным секциям используется объект WebConfigurationManager. Для этого следует воспользоваться статическим методом "OpenWebConfiguration" для получения объекта Configuration, а затем методом "GetSection" для получения доступа к конкретной секции. После этого, эту секцию можно привести к классу-обработчику этой секции и, используя публичные свойства последнего, работать с настройками конфигурации.

    Таким образом, подсистема конфигурации .NET Framework является мощным механизмом, который позволяет, как переопределять стандартные настройки приложения, так и определять собственные параметры конфигурирования.

    Краткие итоги

    Типичная проблема при разработке любого приложения – способ хранения параметров приложения. В .NET Framework существует стандартный механизм, который позволяет решить эту проблему. Все конфигурационные файлы приложения хранятся в формате XML и имеют расширение ".config". При этом существует иерархия файлов конфигурации. На глобальном уровне существует два файла – machine.config и web.config. Настройки, определенные в файле machine.config нельзя переопределять на уровне приложения, а настройки, определенные в файле web.config – можно. На уровне приложения файлы конфигурации также могут наследоваться. Для создания собственной секции настройки необходимо создать класс-обработчик для этой секции и объявить секцию в разделе "configSections". Доступ к настройкам приложения осуществляется на основе объекта WebConfigurationManager.

    Сохранение состояния

    Одной из наиболее важных проблем при разработке веб-приложений является специфика взаимодействия клиента и сервера по протоколу HTTP. Как известно, взаимодействие клиента и сервера по протоколу HTTP происходит в режиме "запрос-ответ", при этом сервер удаляет все данные, специфичные для этого взаимодействия после того, как отправит ответ. Веб-сервер вынужден делать подобную очистку из-за того, что он может обслуживать большое количество клиентов.

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

    Платформа ASP.NET содержит целый ряд инструментов, которые позволяют сохранять состояние между HTTP-запросами:

  • строка запроса (QueryString);
  • состояние вида (ViewState);
  • состояние сенаса (Session);
  • состояние приложения (Application);
  • профили;
  • и другие.
  • Как видно, доступно множество способов сохранения состояния. Они отличаются друг от друга контекстом сохранения состояния или, иначе говоря, временем жизни данных, сохраненных между запросами.

    Строка запроса (QueryString) позволяет сохранять состояние при обращении к текущей или внешней странице сайта. При этом значение состояние передается в строке запроса в качестве параметра GET. Например, такой запрос может выглядеть следующим образом:

    http://localhost/Default.aspx?StateValue=J1HKJH912IRWYQOICNOUQW

    или

    http://localhost/SomePage.aspx?Data=NIWQ7DWIU9WQ

    При таком способе сохранения состояния, после перенаправления пользователя на новую (или ту же самую) страницу, в GET-параметрах передается ее состояние. При этом не имеют значения названия ключей или типы страниц.

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

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

    Состояние вида (ViewState) позволяет передавать состояние между HTTP-запросами в рамках одной и той же страницы.

    Платформа ASP.NET Web Forms (см. далее) содержит специальный способ обработки формы, который называется Postback. Идея этого механизма состоит в том, чтобы отправить данные на сервер с помощью HTTP-метода POST, при котором все поля формы также отправляются на сервер и при этом обращение осуществляется к той же самой странице. После обращения к веб-странице посредством механизма Postback форма как правило совершает определенный цикл обработки и модифицирует содержимое или внешний вид формы.

    Использование механизма ViewState тесно связано с механизмом Postback. Механизм ViewState обеспечивает передачу данных между HTTP-запросами на основе скрытого поля, которое хранится в HTML-коде страницы. Если странице требуется сохранить состояние между запросами Postback, она сохраняет эти значения в скрытом поле HTML. При следующем Postback-запросе эти данные снова будут отправлены на сервер, где будут обработаны. Так процесс может повторяться несколько раз, пока происходят обращения Postback к данной форме. Однако, состояние ViewState теряется при переходе к другой странице.

    Скрытое поле ViewState в коде HTML выглядит следующим образом.

    Однако, если в состояние вида ViewState добавить какие-то данные, то оно видоизменится и может стать таким, как показано ниже.

    Как видно, данные во ViewState хранятся в закодированном виде (не зашифрованном!) по алгоритму base64. При обработке Postback-запроса среда исполнения анализирует содержимое скрытого поля и помещает данные из него в объект ViewState (коллекция "ключ-значение"), который доступен разработчикам. В процессе обработки страницы содержимое объекта ViewState может изменяться, после чего в конце цикла обработки страницы этот объект трансформируется в строку (сериализуется), кодируется при помощи алгоритма base64 и помещается в скрытое поле. Таким образом, для работы с этим способом сохранения состояния необходимо использовать объект ViewState как показано ниже.

    Как правило, при разработке приложений редко приходится напрямую пользоваться объектом ViewState. Обычно, объект ViewState используют элементы управления для сохранения своего состояния. Для управления использованием механизма ViewState у элементов управления есть свойство EnableViewState, которое по умолчанию включено. Если разработчику не требуется подобное поведение для элемента управления, он может отключить использование ViewState для него.

    Приведенный способ сохранения состояния является достаточно удобным, но порождает ряд дополнительных проблем.

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

    Во-вторых, у этого механизма существует потенциальная угроза перехвата и подмены данных в поле ViewState. Для решения проблемы с подменой данных можно включить механизм дополнительной защиты – хеш-код. При включении этого механизма в состоянии вида хранится надежно зашифрованная контрольная сумма. Это позволяет отследить подмену состояния вида и среагировать на эту ситуацию. Для включения этого механизма защиты необходимо установить свойство текущей страницы EnableViewStateMAC в значение True. Однако даже если использовать хеш-коды, данные состояния вида все равно остаются доступными для чтения и существует угроза перехвата этих данных. Чтобы исключить возможность перехвата данных можно включить шифрование состояния вида. Для этого необходимо установить свойство ViewStateEncryptionMode у текущей страницы в значение Always.

    В целом, состояние вида ViewState используется для передачи данных между запросами Postback и в некоторых ситуациях может быть полезным.

    Другим способом сохранения состояния является состояние сеанса (Session) или просто сессия. Этот способ сохранения состояния позволяет сохранять данные на протяжении всего сеанса работы пользователя с веб-приложением. В этом случае нет ограничения на сохранение состояния в рамках одной страницы – состояние сеанса доступно на протяжении работы с каждой страницей.

    Каким образом работает этот механизм? Состояние сеанса базируется на встроенной в браузер возможности сохранять Cookie – небольшие данные, которые хранятся на стороне клиента. Если серверу необходимо сохранить какие-то данные в браузере пользователя, он генерирует специальный HTTP-заголовок, который включается в состав HTTP-ответа. При получении ответа браузер анализирует HTTP-ответ на наличие подобных заголовков и, в случае если такой заголовок обнаружен, сохраняет представленные данные на локальном компьютере в специальной папке. После этого при каждом обращении браузера к данному веб-приложению, браузер будет добавлять специальные HTTP-заголовки к своим HTTP-запросам. Таким образом, у сервера есть возможность сохранить на клиенте какие-то данные и получать к ним доступ при каждом обращении клиента к серверу.

    Механизм Cookie сам по себе в какой-то степени является способом сохранения состояния. Однако, если его использовать как средство сохранения состояния для большого объема данных, то объем передаваемых данных от клиента к серверу и обратно существенно возрастает. Кроме того, существует потенциальная угроза получения данных из Cookie на стороне клиента, поскольку браузер никак не защищает эти данные. Именно поэтому был разработан механизм сохранения состояния сеанса.

    Основная идея состояния сеанса заключается в следующем. При первом обращении браузера к серверу (когда Cookie чистые) сервер создает специальную область памяти, которой назначает уникальный идентификатор. Этот идентификатор сервер записывает в Cookie браузера. После этого браузер каждый раз будет сообщать этот идентификатор серверу. Теперь на сервере можно использовать выделенную для каждого пользователя область памяти для сохранения туда различных данных. При этом каждый пользователь будет иметь доступ к своему участку памяти. В данном случае этот идентификатор является идентификатором сессии. Поскольку пользователи не могут знать идентификаторов сессии других пользователей, сессия в каком-то смысле является более защищенной. Более того, в этом случае в сессии можно сохранять большие объемы информации без необходимости увеличения объема передаваемой информации между клиентом и сервером.

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

    Как правило, в сессии может храниться идентификатор пользователя, его настройки, а также другая специфичная для каждого пользователя информация. Для работы с состоянием сеанса используется объект Session. По аналогии с ViewState, объект Session является коллекцией "ключ-значение", а работать с ним можно следующим образом.

    Важным отличием состояния сеанса Session от состояния вида ViewState заключается в том, что в Session можно сохранять даже те объекты, которые не поддаются сериализации (преобразованию к текстовому представлению). Например, к таким объектам относится объект подключения к SQL Server и др.

    Наконец, последним способом сохранения состояния является состояние приложения Application. От предыдущих способов сохранения состояния его отличает то, что Application содержит глобальную информацию, доступную всем пользователям одновременно в рамках всех страниц веб-приложения. Это означает, что при сохранении информации в объект Application одним пользователем, другой пользователь, обратившись к приложению, также может получить доступ к этой информации. Обычно за сохранение состояния приложения отвечает объект приложения HttpApplication, доступный по имени Application.

    Работа с объектом Application осуществляется аналогичным образом, как и два предыдущих способа. Объект Application – это коллекция "ключ-значение". Для работы с этим объектом может использоваться следующий код.

    Однако, к объекту Application (как и к любым другим глобальным данным) следует относиться осторожно, поскольку глобальные состояния могут вносить дополнительную сложность в программный код.

    Таким образом, видно, что платформа ASP.NET обладает богатыми возможностями по сохранению состояния между HTTP-запросами. В данной лекции мы рассмотрели основные способы сохранения состояния и в каждой конкретной ситуации стоит выбирать именно тот способ, который наиболее удобен в этой ситуации.

    Краткие итоги

    Сложность разработки веб-приложений состоит в том, что взаимодействие по протоколу HTTP не предполагает сохранения состояния между обращениями. Однако, это бывает очень необходимо в большом числе ситуаций. Для этого в ASP.NET существует ряд инструментов для сохранения состояния. Состояние вида ViewState позволяет сохранять состояние между обращениями клиента к одной и той же странице. Состояние сеанса Session позволяет сохранять состояние для конкретного пользователя для всего веб-приложения. Наконец, состояние приложения Application позволяет сохранять глобальное состояние, доступное каждому пользователю в рамках приложения. Все эти способы доступны для работы через коллекции в стиле "ключ-значение" и очень просты в использовании.

    Контрольные вопросы

  • Каким образом происходит компиляция ASP.NET приложения?
  • Как компилируются страницы ASPX?
  • Что такое частичный (partial) класс?
  • Каким образом среда исполнения ASP.NET может отслеживать изменение страниц ASP.NET?
  • Что происходит при изменении страниц ASP.NET на веб-сервере?
  • Для чего нужна предкомпиляция приложения?
  • Какие виды предкомпиляции приложения существуют в ASP.NET?
  • Что такое домен приложения?
  • Что представляет собой механизм отложенной инициализации?
  • Для чего нужны глобальные события приложения?
  • Назовите основные глобальные события приложения.
  • Каким образом можно задать обработчик для глобальных событий приложения?
  • Какие служебные объекты существуют в ASP.NET?
  • Для чего нужны служебные объекты ASP.NET?
  • Чем отличаются зарезервированные папки ASP.NET от обычных папок в составе веб-приложения?
  • Назовите основные типы зарезервированных папок ASP.NET.
  • Каким образом строится конфигурация ASP.NET приложений?
  • Каким образом реализован механизм наследования конфигурации ASP.NET приложений и для чего он необходим?
  • Чем отличаются файлы machine.config и web.config?
  • Какой базовый класс используется для создания класс-обработчика конфигурационной секции ASP.NET?
  • Что такое сохранение состояния?
  • Какие способы сохранения состояния доступны в ASP.NET?
  • Каким образом работает состояние вида (ViewState)?
  • Каким образом работает состояние сеанса (Session)?
  • Каким образом работает состояние приложения (Application)?
  • Приведите примеры правильного применения для каждого из способов сохранения состояния.
  • Вернуться к учебному плану