
Лабораторная работа № 4. Управление состоянием в asp.Net
Редкие веб-страницы работают независимо от других страниц. Веб-приложениям часто требуется отслеживать посещение пользователем иных страниц веб-сайта для персонализации генерируемых страниц или отчетности.
В ASP.NET существует два типа управления состоянием: на клиенте и на сервере. При управлении состоянием на клиенте сведения о пользователе хранятся на компьютере клиента и передаются коду веб-страницы через строку запроса или cookie-файлы. При управлении состоянием на сервере пользователей отслеживают также по URL или cookie, но сведения о них хранятся в памяти сервера или в базе данных.
4.1. Управление состоянием на клиенте
Управление состоянием путем хранения данных состояния на клиенте характеризуется максимальной масштабируемостью. В ASP.NET существует несколько методов хранения на клиенте сведений, необходимых для управления состоянием:
состояние отображения (view state).
С помощью состояния отображения отслеживаются значения в элементах управления. В состояние отображения добавляются пользовательские значения;
состояние элемента управления (control state).
При создании пользовательских элементов управления, требующих поддержки состояния отображения, применяют состояние элемента управления, чтобы гарантировать нормальную работу элемента даже при отключении состояния отображения другими разработчиками;
скрытые поля (hidden fields).
С помощью скрытых полей данные сохраняются в HTML-форме и не отображаются в браузере. Данные становятся доступными после обработки формы.
cookie-файлы.
Cookie-файлы хранят введенные через браузер значения, которые браузер отправляет на сервер при каждом запросе данной страницы. Cookie-файлы — лучший способ хранения состояния, использующегося многими страницами веб-сайта.
строки запроса (query strings).
Строки запроса сохраняют значения в составе URL, поэтому значения видимы для пользователя. Применяются для отправки состояния через электронную почту либо мгновенно — через URL.
Выбор способа управления состоянием
Такие данные состояния, как имя пользователя, его личные настройки или содержимое корзины, могут храниться и на клиенте, и на сервере. В первом случае эти данные передаются на сервер с каждым запросом. При хранении данных состояния на сервере клиенты отслеживаются с помощью методов управления состоянием на клиенте. На рис. 1 представлены оба способа управления состоянием.
Рис. 1. При управлении состоянием на клиенте данные состояния хранятся на клиентском компьютере, а при управлении состоянием на сервере — на серверной машине
Хранение информации на клиенте имеет следующие преимущества:
лучшая масштабируемость.
При управлении состоянием на сервере каждый подключившийся к веб-серверу клиент занимает часть ресурсов сервера. Если сайт одновременно посещают сотни и тысячи пользователей, для хранения их состояния им потребуется много памяти, которая может стать узким местом. Устранить его можно, переложив управление данными состояния на пользователей;
поддержка множества веб-серверов.
При управлении состоянием на клиенте обработку поступающих запросов можно распределить среди множества веб-серверов, не модифицируя код приложения, так как клиент предоставляет веб-серверу всю необходимую информацию. При управлении состоянием на сервере клиент в середине сеанса может обратится к другому серверу, но у того может не оказаться доступа к состоянию клиента. При управлении состоянием на сервере с использованием группы серверов необходима интеллектуальная балансировка нагрузки (пересылающая запросы одного клиента одному и тому же серверу) либо централизованное управление состоянием (когда состояние хранится в центральной базе данных, доступной всем веб-серверам).
Хранение информации на сервере имеет следующие преимущества:
безопасность.
Информация управления состоянием на клиенте может быть похищена с клиентского компьютера или перехвачена в пути, а также она уязвима к модификации злоумышленниками. Следовательно, ни в коем случае не используйте управление состоянием на клиенте для хранения конфиденциальной информации, такой как пароль, уровень доступа или состояние авторизации;
снижение нагрузки на сеть.
При транспортировке больших объемов данных состояния между клиентом и сервером создается значительная нагрузка на канал связи и увеличивается время загрузки страницы, что чревато ростом расходов и снижением масштабируемости. Пересылка больших объемов данных негативно сказывается на эффективности мобильных клиентов, обычно подключенных через медленные каналы связи. Следовательно, громоздкую информацию управления состоянием (более 1 Кб) лучше хранить на сервере.
Состояние отображения ViewState
Несложно заметить, что после щелчка кнопки Submit на странице ASP.NET сохраняются все значения и настройки. Например, если изменить текст и щелкнуть кнопку Submit, внесенные изменения будут отображаться и после перезагрузки страницы. Это происходит благодаря наличию в ASP.NET поддержки управления состоянием на клиенте с помощью свойства ViewState, которое содержит объект-словарь, сохраняющий значения между запросами страницы. После обработки страницы ASP.NET ее текущее состояние, а также элементов управления хэшируется и строка с хэшем сохраняется в скрытом поле страницы. Если данные получаются очень объемными и не помещаются в единственном поле (это зависит от значения свойства MaxPageStateFieldLength), ASP.NET «режет» состояние отображения на части и записывает его в несколько скрытых полей. В следующем примере кода показано сохранение состояния отображения в виде скрытой формы среди HTML-кода страницы:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTIxNDIyOTM0Mg9kFgICAw9kFgICAQ8PFgIeBFRleHQFEzQvNS8yMDA2IDE6Mzc6MTEgUE1kZGROWHn/rt75XF/pMGnqjqHlH66cdw==" />
Шифрование состояния отображения
Чтобы затруднить злонамеренным пользователям доступ к состоянию отображения, следует включить его шифрование. Это необходимо, если в состоянии отображения планируется хранить конфиденциальную информацию, но шифрование увеличивает издержки веб-сервера на обработку. Чтобы включить шифрование состояния отображения в приложении, в файле Web.config присвойте атрибуту <pages viewStateEncryptionMode> значение Always:
<configuration>
<system.web>
<pages viewStateEncryptionMode="Always"/>
</system.web>
</configuration>
Можно включить шифрование состояния отображения только на заданной странице, установив соответствующее значение в директиве page, как показано в следующем примере:
<%@ Page Language="C#" AutoEventWireup=”true" CodeFile=”Default.aspx.cs" Inherits="_Default'’ ViewStateEncryptionMode="Always"%>
Так как объект ViewState поддерживает шифрование, он представляет собой самый безопасный метод управления состоянием на клиенте. Зашифрованные данные ViewState удовлетворяют большинству требований безопасности, но в любом случае безопаснее хранить состояние изображения на сервере.
Отключение ViewState
По умолчанию состояние отображения активно у каждого элемента управления, включая Label, которые почти никогда не изменяются. К сожалению, состояние отображения увеличивает издержки по обработке форм ASP.NET. Чтобы отключить состояние отображения веб-элемента управления, присвойте его свойству EnableViewState значение False. Это ускорит обработку данных на сервере и уменьшит размер страницы.
Чтение и запись данных ViewState
В объект ViewState можно добавлять пользовательские значения (а также извлекать их из него). Самым эффективным и безопасным способом мониторинга состояния какого- либо значения во время посещения пользователем одной веб-страницы ASP.NET является добавление этого значения в ViewState. Однако этот способ не работает на других веб-страницах, и потому ViewState полезен только для временного хранения значений.
Следующий пример кода показывает, как определить, записано ли время последнего визита в ViewState, как вывести его в элементе управления Label с именем Label1 и присвоить значению текущее время. Для использования этого кода создайте форму, добавьте на нее элемент управления Label с именем Label1 и элемент управления Button:
//C#
// Проверить, существует ли объект ViewState, показать результат
if (ViewState["lastVisit"] != null)
Label1.Text = (string)ViewState["lastVisit"];
else
Label1.Text = "lastVisit ViewState not defined.";
// Объявить объект ViewState для следующего просмотра страницы
ViewState.Add("lastVisit", DateTime.Now.ToString());
Cookie-файлы должны содержать строки, а в ViewState можно хранить самые разные объекты, поддерживающие сериализацию. В приведенном ниже примере объект DateTime записывается в ViewState без преобразования в строку, при этом используется другой метод добавления значения в ViewState:
//C#
// Проверить, существует ли объект ViewState, показать результат
if (ViewState["lastVisit"] != null)
Label1.Text = ((DateTime)ViewState["lastVisit"]).ToString();
else
Label1.Text = "lastVisit ViewState not defined.";
// Объявить объект ViewState для следующего просмотра страницы
ViewState["lastVisit"] = DateTime.Now;
Состояние элемента управления
Если созданному вами пользовательскому элементу управления требуется ViewState, другие разработчики могут нарушить его работу, отключив ViewState для целой страницы. Разрешить эту проблему можно путем сохранения сведений о состоянии элемента управления в свойстве ControlState. Оно позволяет сохранить информацию о свойствах заданного элемента управления, и его нельзя отключить, подобно свойству ViewState. Чтобы применить состояние пользовательского элемента управления, нужно переопределить метод OnInit, добавив в него вызов RegisterRequiresControlState во время инициализации, а также переопределить методы SaveControlState и LoadControlState.
ControlState не позволяет разработчику отключить ViewState для элемента управления. Как правило, выбор способа использования элементов управления оставляют разработчикам. Если ваш элемент управления просто не работает без ViewState, необходимо задействовать ControlState.
Скрытые поля
ViewState хранит информацию на веб-странице с помощью скрытых полей, возвращающихся на сервер после отправки формы пользователем, однако их содержимое не отображается веб-браузером (если только пользователь не включит просмотр исходного кода страницы). ASP.NET позволяет создавать пользовательские скрытые поля и сохранять в них значения, отправленные другими формами.
Элемент управления HiddenField хранит единственную переменную в свойстве Value, он должен быть явно добавлен на страницу. Скрытые поля хранят информацию для одной страницы, поэтому бесполезно сохранять в них сеансовые данные. При использовании скрытых полей страницы должны отправляться на сервер с помощью НТТР- метода POST (по щелчку кнопки), а не HTTP GET (по щелчку ссылки). В отличие от состояния отображения, скрытые поля не поддерживают сжатие, шифрование, хэширование и хранение по частям, и потому пользователи могут просматривать или изменять сохраненные в них данные.
Cookie-файлы
Веб-приложения хранят небольшие порции данных для веб-браузера клиента с помощью cookie-файлов. Cookie — небольшая порция данных, хранящихся в текстовом файле на компьютере клиента (это постоянные cookie-файлы) или в сеансовой памяти браузера клиента (такие cookie — временные) Чаще всего cookie-файлы применяют для идентификации пользователя на нескольких веб-страницах. Cookie-файлы также предназначены для хранения данных состояния, персональных настроек пользователя или зашифрованного маркера, указывающего на успешную аутентификацию пользователя.
На рис. 2 показано, как используются cookie-файлы, веб-клиенты и серверы. Сначала (шаг 1) веб клиент запрашивает страницу с сервера. Поскольку он не посещал сервер ранее, у него нет cookie-файла для отправки. Поэтому веб-сервер включает в состав отклика (шаг 2) cookie. Далее веб-клиент отправляет cookie при каждом запросе любой из страниц с того же сервера (шаги 3, 4 и т.д.).
Рис. 2. Веб-серверы используют cookie, чтобы отслеживать веб-клиентов
Применение cookie — наиболее гибкий и надежный способ хранения данных на клиенте. Тем не менее пользователи могут удалить cookie файлы со своих компьютеров в любое время. Даже если cookie-файлам назначен длительный срок действия, их можно удалить вручную. Поэтому при хранении информации с помощью cookie-файлов пользователю необходимо предоставить возможность регистрации в веб-приложении, чтобы он сумел восстановить удаленные cookie-файлы
Чтение и запись Cookie
Веб-приложение создает cookie, посылая их клиенту в заголовке HTTP-отклика. Далее веб-браузер отправляет те же cookie на сервер с каждым запросом.
Чтобы создать cookie, добавьте значение в Response.Cookies объект HttpCookieCollection. Для просмотра cookie, присланного веб-браузером, прочитайте значение Request.Cookies. Следующий пример кода (обработчика события Page_Load) демонстрирует установку и чтение значения cookie с именем «lastVisit», содержащее значение текущего времени. Если у пользователя cookie уже установлен, код отобразит время последнего посещения страницы пользователем в элементе управления Label1.
//C#
// Проверить, существует ли cookie, и показать результат
if (Request.Cookies["lastVisit"] != null)
// Закодировать cookie на случай атаки через клиентский сценарий
Label1.Text = Server.HtmlEncode(Request.Cookies["lastVisit"].Value);
else
Label1.Text = "No value defined";
// Установить cookie для следующего визита
Response.Cookies["lastVisit"].Value = DateTime.Now.ToString();
Response.Cookies["lastVisit"].Expires = DateTime.Now.AddDays(1);
В этом примере показан простейший из распространенных способов создания cookie. Также можно создавать экземпляры класса HttpCookie и добавлять их в HttpCookieCollection.
Если при посещении пользователем страницы из предыдущего примера cookie еще не установлен, код покажет сообщение «No value defined». Если же обновить страницу, код покажет время первого посещения. Заметьте, что этот код задает свойство Expires для cookie. Когда требуется сохранить cookie между запусками браузера, необходимо определить свойство Expires, установив тем самым срок хранения cookie на клиенте. Если свойство Expires не установлено, cookie будет храниться в памяти, пока пользователь не закроет браузер.
Для удаления cookie нужно задать в качестве срока хранения дату из прошлого и перезаписать cookie. Непосредственное же удаление cookie невозможно, поскольку они хранятся на компьютере клиента.
Для устранение неполадок и просмотра cookie следует использовать Trace.axd.
Управление областью видимости cookie
Некоторые веб-сайты хранят в cookie-файлах конфиденциальную информацию, поэтому отправлять их на другие сайты нежелательно. По умолчанию браузер не отправляет cookie на веб-сайт с иным хост-именем (ранее уязвимости зашиты позволяли атакующим «обмануть» браузер, предоставив ему cookie-файлы от другого сайта).
Можно управлять областью видимости cookies, сужая ее до заданной папки веб-сервера или расширяя до любого из серверов домена. Чтобы сузить область видимости cookie до папки, установите свойство Path, как показано в следующем примере:
//C#
Response.Cookies["lastVisit"].Value = DateTime.Now.ToString();
Response.Cookies["lastVisit"].Expires = DateTime.Now.AddDays(1);
Response.Cookies["lastVisit"].Path = "/Application1";
С областью видимости, суженной до «/Application1», браузер отправляет cookie на любую из страниц в папке /Application1, но не на страницы из других папок, даже если последние находятся на том же сервере.
Чтобы расширить область видимости до целого домена, установите свойство Domain, как показано в следующем примере:
//C#
Response.Cookies["lastVisit"].Value = DateTime.Now.ToString();
Response.Cookies["lastVisit"].Expires = DateTime.Now.AddDays(1);
Response.Cookies["lastVisit"].Domain = "contoso.com";
При назначении свойству Domain значения «Contoso.com» браузер будет отправлять cookie любому серверу домена contoso.com, например www.contoso.com, intranet.contoso.com или private.contoso.com. Также можно записать в свойство Domain полное хост-имя, сужающее область видимости cookie до заданного сервера.
Сохранение множества значений в cookie
В зависимости от версии браузера разрешается хранить до 20 cookie-файлов размером до 4 Кб для каждого сайта. Можно обойти это ограничение, записав несколько значений в одном cookie, как показано в следующем коде:
//C#
Response.Cookies["info"]["visit"].Value = DateTime.Now.ToString(); Response.Cookies["info"]["firstName"].Value = "Tony";
Response.Cookies["info"]["border"].Value = "blue";
Response.Cookies["info"].Expires = DateTime.Now.AddDays(1);
Этот код посылает веб-браузеру cookie со значением:
(visit=4/5/2006 2:35:18 PM) (firstName=Tony) (border=blue)
Свойства Expires, Domain и Path действуют на все значения внутри cookie. С помощью Request.Cookies можно получать и устанавливать отдельные значения в cookie.
Строки запроса
Строки запроса обычно используется для хранения переменных, идентифицирующих страницы, критериев поиска и номеров страниц. Строка запроса — это строка, добавленная к URL. Строка запроса может выглядеть так:
http://support.microsoft.com/Default.aspx?kbid=315233
В этом примере URL идентифицирует страницу Default.aspx. Строка запроса (начинающаяся со знака вопроса «?») включает одиночный параметр «kbid» со значением «315233». Строки запроса могут содержать и несколько параметров. В следующем примере поискового запроса для сайта microsoft.com строка запроса определяет язык и критерии поиска.
http://search.microsoft.com/results.aspx?mkt=en-US&setlang=en- US&q=hello+world
Значения этой строки запроса можно извлечь из страницы ASP.NET с помощью объектов, перечисленных в табл. 1.
Табл. 1. Пример значений строки запроса
Имя |
Объект ASP.NET |
Значение |
mkt |
Request.QueryString["mkt"] |
en-US |
setlang |
Request.QueryString["setlang"] |
en-US |
q |
Request.QueryString["q"] |
hello world |
Строки запроса — простой, но ограниченный механизм хранения информации о состоянии между запросами страниц. Например, в строке запроса легко передать количество товара со страницы описания на страницу оформления заказа. Некоторые браузеры и устройства ограничивают длину URL 2083 символами. Другое ограничение состоит в необходимости отправки страницы командой НТТР GЕТ, чтобы значения из строки запроса были доступны для обработки. Следовательно, запросы нельзя добавлять в целевые URL кнопок на формах.
Поскольку пользователи могут изменять информацию в строке запроса, поэтому необходимо всегда проверять на допустимость данные в строке запроса.
Данные строки запроса включаются в закладки, а также в URL, пересылаемые по электронной почте. Фактически, единственный способ разрешить другому пользователю включить в запрос данные состояния — копирование-вставка URL. Поэтому строки запроса следует применять для передачи любой информации, уникально идентифицирующей веб-страницу, даже при использовании других методов управления состоянием.
Браузеры налагают ограничение на длину URL: ее предел — 2083 знака, но проблемы возникают и с более короткими URL, если пользователь отправляет их по электронной почте открытым текстом либо в мгновенном сообщении. По электронной почте разрешена пересылка URL длиной до 70 символов (включая http:// или https*//)- В составе мгновенных сообщений разрешается отправлять URL длиной до 400 знаков.
Никогда не доверяйте значениям из строки запроса; они всегда должны проверяться на допустимость.
Чтобы записать значения в строку запроса, добавьте в URL любую гиперссылку, доступную пользователю. Например, если есть элемент управления HyperLink со свойством NavigateUrl, установленным в «page.aspx», можно добавить строку «?user=tony» в HyperLink.NavigateUrl, чтобы URL приобрел вид «page.aspx?user=tony». Значения в строках запроса разделяют знаками «амперсанд» (&). Например, URL «page.aspx?user=tony&prefs=l&page=1252» посылает странице Page.aspx три значения в строке запроса: user со значением «tony», prefs со значением «1» и page со значением «1252».
Один из самых больших недостатков использования строк запроса — отсутствие в инфраструктуре .NET встроенных инструментов, упрощающих их создание. Поэтому приходится вручную добавлять строки запроса к каждой гиперссылке.
Для чтения значения строки запроса необходимо обратиться к набору Request.QueryStrings, как к cookie. Продолжая предыдущий пример, включим в него код, позволяющий странице page.aspx обрабатывать строку запроса «user», вызывая Request.QueryStrings["user"] в С#. Например, следующий код показывает значения параметров user, prefs и page из строки запроса в элементе управления Label1
//C#
Label1.Text = "User: " + Server.HtmlEncode(Request.QueryString["user"]) +
", Prefs: " + Server.HtmlEncode(Request.QueryString["prefs"]) +
", Page: " + Server.HtmlEncode(Request.QueryString["page"]);
Необходимо всегда кодировать cookie и значения в строке запроса с помощью Server.HtmlEncode перед отображением значения на HTML-страницах. Server.HtmlEncode заменяет HTML-код специальными символами, которые веб-браузер в состоянии только отображать, но не обрабатывать. Например, Server.Html Encode меняет знак «<» на «<». Просматривая значение в браузере, пользователь видит знак «<», но браузер не обработает ни HTML-код, ни клиентские сценарии.
Для обеспечения дополнительной защиты исполняющая среда, обнаружив в строке запроса HTML-код или сценарий, генерирует исключение System.Web.HttpRequestValidationException Следовательно, передавать HTML-код в строке запроса нельзя. Однако это ограничение администратор может отключить, так что не стоит полагаться на него в плане обеспечения безопасности.