Состояние приложения

146

Состояние приложения позволяет сохранять глобальные объекты, доступ к которым может получать любой клиент. В основе состояния приложения лежит класс System.Web.HttpApplicationState, который доступен на всех веб-страницах через встроенный объект Application.

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

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

protected void Page_Load(object sender, EventArgs e)
{        
    int count = 0;        
    if (Application["UsersCount"] != null)            
        count = (int)Application["UsersCount"];        
    
    count++;        
    Application["UsersCount"] = count;        
    lblInfo.Text = "Количество посещений этой страницы: <b>" + count.ToString() + "</b>";}

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

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

  1. Пользователь А извлекает текущее значение счетчика (432).

  2. Пользователь Б извлекает текущее значение счетчика (432).

  3. Пользователь А устанавливает значение счетчика в 433.

  4. Пользователь Б устанавливает значение счетчика в 433.

Другими словами, один из этих запросов не учитывается, потому что два клиента получают доступ к счетчику одновременно. Во избежание этой проблемы, следует использовать методы Lock() и UnLock(), которые явно позволяют получать доступ к коллекции состояния Application только одному клиенту за раз:

protected void Page_Load(object sender, EventArgs e)
{
     // Закрытый доступ        
     Application.Lock();        
     int count = 0;        
     
     if (Application["UsersCount"] != null)            
         count = (int)Application["UsersCount"];        
     
     count++;        
     Application["UsersCount"] = count;        
     
     // Снять закрытый доступ        
     Application.UnLock();        
     lblInfo.Text = "Количество посещений этой страницы: <b>" + count.ToString() + "</b>";
}

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

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

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

Статические переменные приложения

Глобальные переменные приложения можно хранить еще одним способом — путем добавления в файл global.asax статических переменных экземпляра, которые после этого компилируются в специальный класс веб-приложения по имени HttpApplication и делаются доступными для всех входящих в его состав страниц.

Ниже показан пример объявления статического массива строк:

<%@ Application Language="C#" %>

<script runat="server">    
   public static string[] FileList;       
</script>

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

Еще одним требованием, которое обязательно должно выполняться для того, чтобы эта стратегия работала, является наличие возможности получать в коде доступ к статическим переменным экземпляра, которые были добавлены в специальный класс приложения. Чтобы сделать это возможным, необходимо указать имя, которое должно использоваться для этого класса. Для этого устанавливается значение свойства ClassName в директиве Application, которая находится в начале файла global.asax.

Ниже показан пример назначения классу приложения имени Global:

<%@ Application Language="C#" CodeBehind="Global" %>

После этого в веб-страницах можно будет использовать такой код:

string firstEntry = Global.FileList[0];

Добавляемая в файл global.asax переменная экземпляра имеет в точности те же характеристики, что и значение в коллекции Application. Другими словами, можно использовать любой тип данных .NET, значение сохраняется до тех пор, пока не произойдет перезапуск домена приложения, и состояние не разделяется между компьютерами веб-фермы. Однако нет никакого механизма автоматической блокировки. Поскольку множество клиентов могут пытаться получить доступ и изменить ее значение, должен использоваться C#-оператор lock для временного ограничения переменной единственным потоком.

Лучший чат для C# программистов