Опубликован: 13.07.2010 | Уровень: специалист | Доступ: платный
Самостоятельная работа 35:

Управление состоянием страниц на клиенте

Упражнение 8. Cookie-наборы

Куки-наборы являются еще одним распространенным способом хранения информации на клиенте. Они, подобно состоянию вида, также включают в себя текстовую информацию, но хранят ее не в скрытых полях HTML-документа, а в небольших файлах, хранящихся на клиенте в специальной временной папке (...\ Temporary Internet Files \). В зависимости от версии броузера для каждого сайта у клиента таких файлов может быть до 20 штук с размером до 4 Кб каждый. Клиент может даже и не подозревать, что при взаимодействии с сайтом его броузер автоматически принимает и отсылает связанные с ним куки.

Однако клиент имеет возможность удалить куки, отключить в броузере поддержку куков, прочитать информацию в любом из них и опасно ее модифицировать. Такую возможность разработчик страницы должен учитывать и применять соответствующие методы. В свою очередь, разработчик страницы может устанавливать время жизни куков и шифровать сохраняемую в них информацию. Использование куков опирается на два объекта-свойства страницы Request и Response, каждый из которых имеет коллекцию (набор) Cookies типа HttpCookieCollection. Поступившие от клиента куки нужно извлекать из коллекции Request.Cookies, а отправляемые на клиента новые данные для их сохранения в куках необходимо добавлять в коллекцию Response.Cookies.

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

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

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

  • Добавьте к проекту страницу без отделенного кода с именем CookiesPage1.aspx и назначьте ее стартовой

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

<%@ Page Language="C#" ValidateRequest="false" %>
    
<script runat="server">
        
    // Вынесли как поле для видимости в обработчиках
    // Инициализировать необязательно, компилятор сам справится
    TextBox txtName;
    String result = "";
    int count = 0;
    
    protected void Page_Load(object sender, EventArgs e)
    {
        this.Response.Write("<h1 style='color: Red;'>Испытание Cookies</h1>");
    
        Label label = new Label();
        label.Text = "Введите ФИО:&nbsp;&nbsp;";
        form1.Controls.Add(label);
    
        txtName = new TextBox();
        txtName.Text = "Снетков В.М.";
        form1.Controls.Add(txtName);
    
        form1.Controls.Add(new HtmlGenericControl("br"));
    
        Button button = new Button();
        button.Text = "Отправить";
        form1.Controls.Add(button);// Просто кнопка Submit
    
        button = new Button();
        button.Text = "Сохранить";
        form1.Controls.Add(button);
        button.Click += btnSaveClick;
    
        button = new Button();
        button.Text = "Восстановить";
        form1.Controls.Add(button);
        button.Click += btnRestoreClick;
    
        form1.Controls.Add(new HtmlGenericControl("br"));
    
        button = new Button();
        button.Text = "Удалить";
        form1.Controls.Add(button);
        button.Click += btnDeleteClick;
    
        form1.Controls.Add(new HtmlGenericControl("br"));
    
        HyperLink link = new HyperLink();
        form1.Controls.Add(link);
        // Определяем имя класса текущей страницы и формируем гиперссылку
        String nameCurrentPage = this.GetType().Name;
        // Разбиваем строку на части
        String[] parts = nameCurrentPage.Split('_');
        // Сравниваем первую часть без учета регистра
        if (String.Compare(parts[0], "CookiesPage1", true) == 0)
            link.NavigateUrl = "CookiesPage2" + ".aspx";
        else
            link.NavigateUrl = "CookiesPage1" + ".aspx";
        link.Text = "Перейти к " + link.NavigateUrl;
    
        // Отключаем сохранение состояния вида всех элементов страницы
        DisabledViewState(form1.Controls);
    
        // Извлекаем общее число посещений 
        HttpCookie cookie = this.Request.Cookies["count"];
        if (cookie != null)
        {
            int.TryParse(cookie.Value, out count);
        }
        count++;
        // Адресуемся к отклику и сохраняем общее число посещений
        cookie = this.Response.Cookies["count"];
        cookie.Value = count.ToString();
        // Устанавливаем срок хранения на клиенте: от текущего 1 день 
        cookie.Expires = DateTime.Now.AddDays(1);
        // Отображаем сообщение на странице пользователя
        result = "Текущий запрос: " + count.ToString();
    }
    
    // Отключаем механизм сохранения состояния вида для всех элементов
    void DisabledViewState(ControlCollection controls)
    {
        foreach (Control ctrl in controls)
        {
            // Отключаем сохранение состояния вида элемента
            if (ctrl.EnableViewState)
                ctrl.EnableViewState = false;
    
            // Продолжаем опрашивать рекурсивно, если есть дочерние элементы
            if (ctrl.Controls != null)
                DisabledViewState(ctrl.Controls);
        }
    }
    
    // Переопределяем обработчик генерации HTML-вывода страницы
    protected override void Render(HtmlTextWriter output)
    {
        // Центрируем динамически созданные элементы управления
        output.RenderBeginTag(HtmlTextWriterTag.Center);// Открывающий дескриптор
        base.Render(output);    // Выгружаем основной рендеринг
        output.Write("<hr />");
        output.Write(result);
        output.RenderEndTag();                          // Закрывающий дескриптор
    }
    
    // Сохраняем данные в куке "oldInfo"
    void btnSaveClick(object sender, EventArgs e)
    {
        // Создаем словарь и заполняем текущей информацией 
        HttpCookie cookie = new HttpCookie("oldInfo");
        // Кодировать необязательно, направляется в файл без исполнения
        cookie["name"] = txtName.Text;
        cookie["clicks"] = count.ToString();
        // Присоединяем к ответу
        // Срок хранения - текущий сеанс
        // cookie.Expires = DateTime.Now.AddYears(1);
        this.Response.Cookies.Add(cookie);
    }
    
    // Извлекаем данные из кука "oldInfo"
    void btnRestoreClick(object sender, EventArgs e)
    {
        // Подключаемся к кукам поступившего запроса
        HttpCookieCollection cookies = this.Request.Cookies;
        // Извлекаем нужный словарь
        HttpCookie cookie = cookies["oldInfo"];
        if (cookie != null)
        {
            // Извлекаем значения словаря
            string name, clicks;
            name = cookie["name"];
            clicks = cookie["clicks"];
            
            // Альтернативный синтаксис
            // name = cookies["oldInfo"]["name"];
            // clicks = cookies["oldInfo"]["clicks"];
            // Если готовим к отправке, то можно...
            // cookies["oldInfo"].Expires = DateTime.Now.AddYears(1);
            
            // Безопасно отображаем сохраненные значения,
            // кодируя интерпретируемые броузером символы
            name = Server.HtmlEncode(name);
            clicks = Server.HtmlEncode(clicks);
            result = "Сохраненное ФИО: " + name;
            result += "; cохраненный запрос: " + clicks;
        }
        else
        {
            result = "Нечего восстанавливать! ";
            result += "Текущий запрос: " + count.ToString();
        }
    }
    
    // Удаляем кук "count"
    void btnDeleteClick(object sender, EventArgs e)
    {
        // Удаляем кук количества запросов, посылая просроченную дату
        this.Response.Cookies["count"].Expires = 
            DateTime.Now.AddDays(-1);
    }
</script>
    
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        
    </div>
    </form>
</body>
</html>
Листинг 35.20. Код страницы CookiesPage1.aspx

Атрибут ValidateRequest = "false" директивы @Page отключает контроль системы за вводом опасного кода в текстовые поля.

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

  • Создайте в проекте копию страницы CookiesPage1.aspx и присвойте ей имя CookiesPage2.aspx
  • Запустите первую страницу CookiesPage1.aspx и испытайте ее работу совместно со второй страницей, переходя друг на друга по гиперссылке внизу страниц

Вид страницы на клиенте будет таким


Упражнение 9. Проверка поддержки механизма cookies клиентом

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

Информация о функциональности броузера автоматически поступает на сервер с каждым запросом пользователя и записывается в объект типа System.Web. HttpBrowserCapabilities, ссылка на который находится в свойстве Page.Request. Browser объекта страницы. Эта информация включает в себя тип броузера, его версию, поддержку на стороне клиента сценариев, стилей, фреймов, cookie, таблиц и другое.

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

Что касается cookie -наборов, то при первом запросе страницы после того, как мы удостоверимся, что такая функциональность у броузера имеется, можно сделать пробный отклик с посылкой cookie -набора с помощью метода Response. Redirect(), а затем в поступившем запросе попытаться прочитать его. Если такая попытка не удастся, значит поддержка cookies -наборов недоступна и нужно переключать логику выполнение страницы на другие методы сохранения межстраничной информации.

  • В качестве иллюстрации к сказанному создайте страницу с именем IsCookiesSupported.aspx и заполните ее следующим кодом
<%@ Page Language="C#" ClassName="IsCookiesSupported" EnableViewState="false" %>
    
<script runat="server">
    
    bool CookiesSupportedFlag;
    int clicks = 1;
    
    protected void Page_Load(object sender, EventArgs e)
    {
        // Создаем объекты пользовательского интерфейса
        // Текстовая метка заголовка
        Label label = new Label();
        form1.Controls.Add(label);
        label.Text = "<h2>Поддерживаются ли Cookies?</h2>";
    
        // Кнопка Submit
        Button submit = new Button();
        form1.Controls.Add(submit);
        submit.Text = "Отправить";
    
        // Горизонтальная линия
        form1.Controls.Add(new HtmlGenericControl(HtmlTextWriterTag.Hr.ToString()));
        //form1.Controls.Add(new HtmlGenericControl("hr")); // То же самое
    
        // Текстовая метка результатов
        label = new Label();
        form1.Controls.Add(label);
    
        if (!this.IsPostBack)
        {
            // Проверяем функциональность броузера на поддержку Cookies
            CookiesSupportedFlag = this.Request.Browser.Cookies;
            if (CookiesSupportedFlag)
            {
                // Для дополнительной проверки пробуем отправить cookie
                this.Response.Cookies["tmp"].Value = String.Empty;
            }
    
            // this.Response.Redirect(Request.Url.ToString());// Зацикливает
    
            // Дублируем сохранение в состоянии вида
            this.ViewState["clicks"] = clicks.ToString(); ;
            label.Text = "Пробный запрос страницы";
            return;
        }
        else if (this.Request.Cookies["tmp"] != null)
        {
            // Поднимаем флаг поддержки cookies
            CookiesSupportedFlag = true;
        }
        else
            CookiesSupportedFlag = false;
    
        // Принимаем решение, откуда извлекать и где сохранять данные
        if (CookiesSupportedFlag)
        {
            if (this.Request.Cookies["clicks"] != null)
                clicks = int.Parse(this.Request.Cookies["clicks"].Value) + 1;
            this.Response.Cookies["clicks"].Value = clicks.ToString();
            // Устанавливаем срок хранения
            this.Response.Cookies["clicks"].Expires =
                DateTime.Now.AddMinutes(1.0);
        }
        else
        {
            clicks = Convert.ToInt32(this.ViewState["clicks"]) + 1;
            this.ViewState["clicks"] = clicks.ToString();
        }  
        
        // Показываем пользователю количество щелчков и механизм хранения
        label.Text = "Запрос страницы № " + clicks.ToString();
        if (CookiesSupportedFlag)
            label.Text += "<br />Хранение в cookies";
        else
            label.Text += "<br />Хранение в ViewState";   
    }
</script>
    
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    </form>
</body>
</html>
Листинг 35.21. Код страницы IsCookiesSupported.aspx для проверки поддержки Cookies

Cookie -набору можно назначить область видимости в пределах сайта. В этом случае броузер будет следить за тем, при работе с какими страницами отправлять на них куки, а на какие не отправлять. Так, например, если кук "clicks" при записи пометить

Response.Cookies["clicks"].Path = "~/MyFolder";

то кук будет доступен только страницам папки MyFolder. По умолчанию кук доступен любым страницам домена, как и, например, при установке

Response.Cookies["clicks"].Domain = "mysite.ru";

Александр Очеретяный
Александр Очеретяный
Украина, Киев
Анастасия Балыбердина
Анастасия Балыбердина
Украина, Киев, НТУУ КПИ