Состояние представления

181

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

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

ViewState["Counter"] = 1;

Этот код помещает в коллекцию ViewState значение 1 (точнее, целое число, которое содержит значение 1) и присваивает ему описательное имя Counter. Если в коллекции ViewState в текущий момент нет элемента с именем Counter, он будет добавлен в нее автоматически. Если элемент с таким именем уже существует, он будет заменен.

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

Ниже показан код, который извлекает значение Counter и преобразует его в целое число:

int counter;
if (ViewState["Counter"] != null)        
   counter = (int)ViewState["Counter"];

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

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

Пример использования состояния представления

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

<form id="form1" runat="server">    
<div>
   <table>
      <tr>
         <td>Имя:</td>
         <td><asp:TextBox runat="server" Width="200px" ID="Name" /></td>
      </tr>
      <tr>
         <td>ID:</td>
         <td><asp:TextBox runat="server" Width="200px" ID="EmpID" /></td>
      </tr>
      <tr>
         <td>Возраст:</td>
         <td><asp:TextBox runat="server" Width="200px" ID="Age" /></td>
      </tr>
      <tr>
         <td>E-mail:</td>
         <td><asp:TextBox runat="server" Width="200px" ID="Email" /></td>
      </tr>
      <tr>
         <td>Пароль:</td>
         <td><asp:TextBox TextMode="Password" runat="server" Width="200px" ID="Password" /></td>
      </tr>
   </table>
   <br /><asp:Button runat="server" Text="Save" ID="cmdSave" OnClick="cmdSave_Click" />
    <asp:Button ID="cmdRestore" runat="server" Text="Restore" OnClick="cmdRestore_Click"></asp:Button><br />
</div></form>
public partial class ViewState : System.Web.UI.Page
{  
   protected void Page_Load(object sender, EventArgs e)    {    }    
   protected void cmdSave_Click(object sender, EventArgs e)    
   {        
       // Сохранить текущий текст        
       SaveAllText(Page.Controls, true);    
   }
   
   private void SaveAllText(ControlCollection controls, bool saveNested)    
   {        
       foreach (Control control in controls)       
       {            
           if (control is TextBox)            
           {                
               // Сохранить текст с использованием уникального                 
               // идентификатора элемента управления                
               ViewState[control.ID] = ((TextBox)control).Text;  
           }            
           
           if ((control.Controls != null) && saveNested)            
           {                
               SaveAllText(control.Controls, true);            
           }        
        }    
    }    
    
    private void RestoreAllText(ControlCollection controls, bool saveNested)    
    {        
        foreach (Control control in controls)        
        {            
            if (control is TextBox)            
            {                
                if (ViewState[control.ID] != null)                    
                    ((TextBox)control).Text = (string)ViewState[control.ID];            
            }            
            
            if ((control.Controls != null) && saveNested)            
            {                
                 RestoreAllText(control.Controls, true); 
            }        
         }    
     }
     
     protected void cmdRestore_Click(object sender, EventArgs e)    
     {        
         // Извлечь последний сохраненный текст        
         RestoreAllText(Page.Controls, true);    
     }
}

На рисунке показана эта страница в действии:

Сохранение и восстановление текста с использованием состояния представления

Сохранение объектов в состоянии представления

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

Чтобы сделать объекты сериализируемыми, перед объявлением соответствующего класса необходимо поместить атрибут Serializable:

[Serializable]
public class Customer
{    
    public string FirstName;    
    public int ID;    
    public byte Age;    
    public string Email;    
    public Customer(string firstname, int id, byte age, string email)    
    {        
        FirstName = firstname;        
        ID = id;        
        Age = age;       
        Email = email;    
    }
}

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

Customer cust = new Customer("Вася", 5, 6, "vasya@gmail.com");
ViewState["CurrentCustomer"] = cust;

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

Чтобы класс был сериализируемым, он должен отвечать перечисленным ниже требованиям:

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

Ниже показан измененный код страницы из приведенного ранее примера, в котором теперь применяется обобщенный класс Dictionary. Этот класс представляет собой сериализируемую коллекцию элементов "ключ-значение", определенную в пространстве имен System.Collections.Generic. При условии, что класс Dictionary применяется для хранения сериализируемых объектов (и для ключей используется сериализируемый тип данных), объект Dictionary можно сохранить в состоянии представления без особых проблем.

Для демонстрации этого приема в следующем примере вся информация об элементах управления страницы сохраняется в виде коллекции строк в объекте Dictionary, при этом каждый элемент индексируется по строке с идентификатором элемента управления. Готовый объект Dictionary сохраняется в состоянии представления для этой страницы. Когда пользователь щелкает на кнопке Display (Отобразить), коллекция Dictionary извлекается, и вся содержащаяся в ней информация отображается в элементе управления Label:

public partial class ViewState : System.Web.UI.Page
{    
    protected void cmdSave_Click(object sender, EventArgs e)    
    {        
        // Поместить текст в словарь        
        var textToSave = new Dictionary<string, string>();        
        SaveAllText(Page.Controls, textToSave, true);        
        
        // Сохранить всю коллекцию в состоянии представления        
        ViewState["ControlText"] = textToSave;    
    }    
    
    private void SaveAllText(ControlCollection controls, 
        Dictionary<string, string> textToSave, bool saveNested)    
    {        
        foreach (Control control in controls)        
        {            
            if (control is TextBox)            
            {                
                // Добавить текст в словарь                
                textToSave.Add(control.ID, ((TextBox)control).Text);            
            }            
            
            if ((control.Controls != null) && saveNested)            
            {                
                SaveAllText(control.Controls, textToSave, true);           
            }        
        }    
    }    
    
    protected void cmdRestore_Click(object sender, EventArgs e)   
    {        
        if (ViewState["ControlText"] != null)        
        {             
            // Извлечь словарь            
            var savedText = (Dictionary<string, string>)ViewState["ControlText"];            
            
            // Отобразить весь текст за счет прохода по словарю            
            lblResults.Text = "";            
            foreach (KeyValuePair<string, string> item in savedText)            
            {                
                lblResults.Text += "<b>" + item.Key + "</b> = " + item.Value + "<br />";            
            }        
        }   
    }
}

На рисунке показаны результаты тестирования этого кода, полученные после ввода некоторых данных, их сохранения и извлечения:

Извлечение объекта из состояния представления

Преимущества использования состояния представления

Состояние представления является идеальным вариантом, т.к. не использует память сервера и не приводит к появлению случайных ограничений (вроде тайм-аута). Итак, что же может заставить отдать предпочтение не состоянию представления, а какому-то другому типу управления состоянием? Ниже перечислены три возможных причины:

Объем пространства, используемый состоянием представления, зависит от количества элементов управления, их сложности и объема динамической информации. Если требуется создать профиль использования состояния представления на странице, просто включите функцию трассировки, добавив в директиву Page атрибут Trace, как показано ниже:

<%@ Page Language="C#" Trace="true" ... %>

Отыщите раздел Control Tree (Дерево элементов управления). Итоговых сведений о состоянии представления, используемом страницей, вы в этом разделе не найдете, но зато в столбце Viewstate Size Bytes (Состояние представления/размер в байтах) будут содержаться сведения о состоянии представления, используемом каждым отдельным элементом управления этой страницы. На столбец Render Size Bytes (Визуализация/размер в байтах) можно не обращать внимания, он просто отражает размер визуализированных HTML-данных для каждого элемента управления:

Определение размера состояния представления, используемого на странице

Выборочное отключение состояния представления

Чтобы повысить скорость передачи данных на странице, состояние представления, если оно не нужно, лучше отключить. Хотя это можно сделать на уровне приложения или на уровне страницы, наиболее правильный вариант — отключить его на уровне элементов управления. Состояние представления для элемента управления не требуется в трех следующих случаях:

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

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

Чтобы отключить состояние представления для одиночного элемента управления, установите свойство EnableViewState этого элемента управления в false. Чтобы отключить состояние представления для всей страницы и всех отображаемых на ней элементов управления, установите свойство EnableViewState этой страницы в false либо используйте в директиве Page атрибут EnableViewState, как показано ниже:

<%@ Page Language="C#" EnableViewState="false" ... %>

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

Чтобы отключить состояние представления сразу для всех веб-страниц в приложении, установите атрибут enableViewState в элементе <pages> файла web.config так, как показано ниже:

<system.web>    <pages enableViewState="false"/></system.web>

При желании включить состояние представления для определенной страницы, нужно установить атрибут EnableViewState директивы Page в true.

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

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

Этот подход предполагает использование еще одного свойства — ViewStateMode. Как и EnableViewState, свойство ViewStateMode применяется ко всем элементам управления и странице и может устанавливаться как в дескрипторе элемента управления, так и в атрибуте директивы Page. Свойство ViewStateMode может быть установлено в одно из следующих трех значений:

Enabled

Состояние представления будет использоваться при условии, что это разрешено свойством EnableViewState.

Disabled

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

Inherit

Элемент управления будет использовать значение ViewStateMode своего контейнера. Это значение по умолчанию.

Для использования выборочного управления состоянием свойство ViewStateMode страницы устанавливается в Disabled. Это приводит к отключению состояния представления для страницы верхнего уровня. По умолчанию свойство ViewStateMode всех элементов управления этой страницы будет установлено в Inherit, а это значит, что состояние представления в них тоже отключено.

<%@ Page Language="C#" ViewStateMode="Disabled" ... %> 

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

Чтобы выборочно включить состояние представления для определенного элемента управления на странице, нужно просто установить его свойство ViewStateMode в Enabled:

<asp:Label ViewStateMode="Enabled" ... />

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

Безопасность состояния представления

Информация состояния представления хранится в виде одной строки формата Base64, которая выглядит примерно так:

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE"    
    value="/wEPDwUKMTM5OTEzODgwOWRkUeWW9zyPw3KoRw2QOJiSLr8E/C57dUsLqT9hx4Ks8oQ=" />

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

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

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

Чтобы отключить средство вычисления хеш-кодов, установите соответствующим образом свойство EnableViewStateMac директивы Page в файле .aspx или атрибут enableViewStateMac элемента <pages> в файле web.config:

<system.web>    <pages enableViewStateMac="false"/></system.web>

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

Даже когда используются хеш-коды, данные состояния сеанса все равно остаются доступными для чтения. Чтобы полностью исключить возможность получения пользователями доступа к любой информации в состоянии представления, следует включить шифрование состояния представления. Включить шифрование для отдельной страницы можно с помощью свойства ViewStateEncryptionMode директивы Page или же можно установить тот же атрибут в конфигурационном файле web.config:

<pages viewStateEncryptionMode="Always" />

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

Для запроса шифрования элемент управления должен вызывать метод Page.RegisterRequiresViewStateEncryption() в какой-то точке своего жизненного цикла, перед генерацией его HTML-разметки. Если ни один из элементов управления не вызывает этот метод, чтобы уведомить о наличии в нем секретной информации, данные состояния представления не шифруются и, следовательно, связанные с шифрованием накладные расходы не возникают.

Однако абсолютной властью элемент управления не обладает — если он вызывает метод Page.RegisterRequiresViewStateEncryption(), а для режима шифрования страницы указано значение Never, данные состояния представления шифроваться не будут.

При хешировании и шифровании данных ASP.NET использует специфический ключ данного компьютера, определенный в разделе <machineKey> файла machine.config. По умолчанию увидеть фактическое определение для раздела <machineKey> не получится, потому что оно инициализируется программно. Однако эквивалентное содержимое можно будет найти в файлах machine.config.comments. Можно явно добавить элемент <machineKey> и настроить его параметры.

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

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