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

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

Упражнение 6. Строки запроса

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

  1. Информация является видимой для пользователя и доступна для вмешательства с его стороны. В результате страница может получить такие данные, от которых она не имеет защиты
  2. Информация должна кодироваться допустимыми для URL символами, например, пробел заменяется парой 20 и т.д.
  3. Броузеры устанавливают ограничение на длину URL, которая должна быть в пределах 1-2 Кб. Поэтому в строку запроса нельзя помещать большое количество информации
  4. Форма отправляющей страницы должна использовать метод передачи GET вместо POST
  5. Формирование строки запроса и извлечение из нее информации программист выполняет вручную

Отправляющая запрос страница инициирует передачу либо с помощью гиперссылки (элемент HyperLink ), либо с помощью кнопки Submit, в обработчик которой вставлен код Response. Redirect( URL ). Для извлечения информации из строки запроса на адресуемой (целевой) странице используется встроенный в нее объект-словарь Request. QueryString.

  • Добавьте к проекту страницу без файла отделенного кода с именем SourceQueryString.aspx и назначьте ее стартовой
  • Добавьте внутрь контейнера <div></div> дескриптор строки-заголовка

<h1 style="color: Red">Введите учетную запись</h1>

  • Дополните открывающий дескриптор <div> встроенным стилем центрирования

<div style="text-align: center">

  • Создайте на Web-форме позиционирующую таблицу размером 3x2 и поместите в ее ячейки из вкладки Standard панели Toolbox серверные элементы с настройками согласно таблицы
Таблица свойств элементов управления страницы SourceQueryString.aspx
Элемент Свойство
Label ID="Label1"
  Text="Имя:"
TextBox ID="Name"
Label ID="Label2"
  Text="Пароль:"
TextBox ID="Password"
  TextMode="Password"
Button ID="Button1"
  Text="Отправить"

Интерфейс страницы в режиме проектирования будет таким


  • Переведите страницу в режим Design и двойным щелчком на элементе Button1 создайте для него обработчик. Заполните обработчик следующим кодом
protected void Button1_Click(object sender, EventArgs e)
    {
        string Url = "TargetQueryString.aspx?Name="
                                + Server.UrlEncode(Name.Text)
                                + "&Password="
                                + Server.UrlEncode(Password.Text);
        this.Response.Redirect(Url);
        
        // То же самое, только с прямой переадресацией
        // this.Server.Transfer(Url);
    }
Листинг 35.14. Код обработчика события Click элемента Button1

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

  • Переведите страницу в режим Design и двойным щелчком создайте в кодовой части обработчик Page_Load(). Заполните обработчик кодом, который восстанавливает состояние текстового поля Name сохраненным в строке запроса значением
protected void Page_Load(object sender, EventArgs e)
    {
        if (this.Request.QueryString["Name"] != null && !this.IsPostBack)
            Name.Text = this.Request.QueryString["Name"];
    }
Листинг 35.15. Код обработчика события Page_Load() страницы SourceQueryString.aspx

Страница SourceQueryString.aspx, отправляющая строку запроса, готова. Вот ее полный код

<%@ Page Language="C#" %>
    
<script runat="server">
    
    protected void Button1_Click(object sender, EventArgs e)
    {
        string Url = "TargetQueryString.aspx?Name="
                                + Server.UrlEncode(Name.Text)
                                + "&Password="
                                + Server.UrlEncode(Password.Text);
        this.Response.Redirect(Url);
        
        // То же самое с прямой переадресацией
        // this.Server.Transfer(Url);
    }
    
    protected void Page_Load(object sender, EventArgs e)
    {
        if (this.Request.QueryString["Name"] != null && !this.IsPostBack)
            Name.Text = this.Request.QueryString["Name"];
    }
</script>
    
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <div style="text-align: center">
            <h1 style="color: Red">
                Введите учетную запись</h1>
            <table>
                <tr>
                    <td style="text-align: right">
                        &nbsp;<asp:Label ID="Label1" runat="server" Text="Имя:" />
                    </td>
                    <td>
                        &nbsp;<asp:TextBox ID="Name" runat="server" />
                    </td>
                </tr>
                <tr>
                    <td style="text-align: right">
                        &nbsp;<asp:Label ID="Label2" runat="server" Text="Пароль:" />
                    </td>
                    <td>
                        &nbsp;<asp:TextBox ID="Password" runat="server" TextMode="Password" />
                    </td>
                </tr>
                <tr>
                    <td colspan="2" style="text-align: center">
                        <asp:Button ID="Button1" runat="server" Text="Отправить" 
                             OnClick="Button1_Click" />
                    </td>
                </tr>
            </table>
        </div>
    </form>
</body>
</html>
Листинг 35.16. Полный код отправляющей страницы SourceQueryString.aspx

Теперь нужно создать принимающую запрос (целевую) страницу. На целевой странице значения поступивших параметров, извлекаемые из строки запроса, следует предварительно URL-раскодировать. Затем можно принять решение, что делать с поступившими данными.

  • Создайте страницу с совмещенным кодом и именем TargetQueryString.aspx
  • Заполните страницу следующим кодом
<%@ Page Language="C#" %>
    
<script runat="server">
    
    protected void Page_Load(object sender, EventArgs e)
    {
        // Критерии оценки зарегистрированного пользователя
        const string NAME = "USER", PASSWORD = "ROOT";
    
        // Извлекаем и раскодируем поступившие данные из строки запроса
        string name = this.Request.QueryString["Name"];
        name = this.Server.UrlDecode(name);
        string password = this.Request.QueryString["Password"];
        password = this.Server.UrlDecode(password);
    
        // Создаем текстовую метку для отображения решения
        Label label = new Label();
        form1.Controls.Add(label);
    
        // Резервируем место для размещения гиперссылки
        PlaceHolder placeHiperLink = new PlaceHolder();
        form1.Controls.Add(placeHiperLink);
    
        // Создаем муляж кнопки
        Button button = new Button();
        form1.Controls.Add(button);
    
        // Анализируем данные и принимаем решение
        StringBuilder message = new StringBuilder();
        if (name.ToUpper() == NAME && password.ToUpper() == PASSWORD)
        {
            // Просто выведем хвалебное приветствие 
            message.Append("<h1 style=\"color: Red\">");
            message.Append("Приветствуем Вас, дорогой " + name + "!");
            message.Append("</h1>");
    
            // Отключаем у кнопки способность инициировать обратную отсылку
            // Лень посылать дальше зарегистрированного пользователя 
            button.PostBackUrl = "javascript: void(0)";
            button.Text = "Дальше >";
    
            // Добавляем в зарезервированное место гиперссылку
            HyperLink link = new HyperLink();
            placeHiperLink.Controls.Add(link);
            link.Text = "< Назад";
            link.NavigateUrl = "~/SourceQueryString.aspx?"
                + "Name=" + name;
    
            // Добавляем в зарезервированное место пробелы
            Label lblSpace = new Label();
            placeHiperLink.Controls.Add(lblSpace);
            lblSpace.Text = "&nbsp;&nbsp;&nbsp;&nbsp;";
        }
        else
        {
            // Поругаем и отправим на повторную регистрацию
            message.Append("<h1>");
            message.Append("Вы неопознаны!<br />Повторите регистрацию...");
            message.Append("</h1>");
    
            button.PostBackUrl = "~/SourceQueryString.aspx?"
                + "Name=" + name;
            button.Text = "< Назад";
        }
    
        // Выводим сформированное сообщение
        label.Text = message.ToString();
    }
</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.17. Полный код страницы TargetQueryString.aspx

Обратите внимание, что интерфейсная часть целевой страницы совершенно пустая - все создается динамически!!!

  • Запустите страницу SourceQueryString.aspx на выполнение и убедитесь, что механизм межстраничного обмена информацией через строку запроса работает

Страница TargetQueryString.aspx на клиенте при наведении курсора мыши на гиперссылке будет отображать переданное с другой страницы имя пользователя

Упражнение 7. Межстраничная обратная отсылка данных

В классах кнопочных элементов управления Button, ImageButton и LinkButton имеется свойство PostBackUrl, позволяющее сразу адресоваться к целевой странице с доступом в ней ко всем данным текущей страницы. Свойству PostBackUrl кнопки обратной отсылки присваивается строка с URL целевой страницы, которая и будет загружена на выполнение по щелчку пользователя на этой кнопке. При этом на целевой странице, благодаря ее свойству PreviousPage, будет полностью доступен объект страницы, инициировавшей обратную отсылку.

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

То же самое происходит и с исходной страницей, загруженной по кнопке, у которой свойство PostBackUrl адресует другую страницу. Исходная страница также проходит все начальные стадии жизни, включая выполнение обработчиков. Непосредственно перед генерацией HTML-кода выполнение исходной страницы прерывается, но она продолжает оставаться в памяти сервера. Одновременно запускается объект целевой страницы, которому в свойство PreviousPage среда исполнения записывает адрес объекта исходной страницы.

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

Подобная межстраничная связь может быть реализована не только как "один к одному", но и в других сочетаниях: "один ко многим", "многие к одному" или "многие ко многим". Множественность на исходной странице может быть организована программным формированием значения свойства PostBackUrl в зависимости от выбранных на этой странице предпочтений. Множественность на целевой странице реализуется проверкой того, к какому типу (к какой странице) относится записанный в свойство PreviousPage объект.

Межстраничная обратная отсылка целевой страницы может быть инициирована не только по щелчку на кнопке с ненулевым значением свойства PostBackUrl, но и в любом месте кода вызовом соответствующей перегрузки метода

public void Page.Server.Transfer(string path, bool preserveForm)

где path -                вызываемая целевая страница        preserveForm - флаг передачи в целевую страницу данных QueryString и Form

Оптимальным будет вызов

Server.Transfer(Url, true);

Проиллюстрируем межстраничную отсылку на примере двух страниц-источников и двух страниц-приемников.

  • Добавьте к проекту страницу с совмещенным кодом и именем SourcePostBack1.aspx
  • Назначьте страницу SourcePostBack1.aspx стартовой и оформите пользовательский интерфейс на этапе проектирования так, как показано на рисунке


Здесь использованы серверные элементы Label, TextBox, ListBox, Button и HyperLink.

  • Для элемента управления ListBox включите свойство AutoPostBack
  • Заполните страницу SourcePostBack1.aspx следующим кодом
<%@ Page Language="C#" %>
    
<script runat="server">
    
    // Функция доступа к закрытому полю класса из внешнего кода
    public string GetName()
    {
        return txtName.Text;
    }
    
    // Свойство доступа к закрытым данным класса из внешнего кода
    public string GetTitle
    {
        get { return lblTitle.Text; }
    }
    
    string text;
    
    protected void Page_Load(object sender, EventArgs e)
    {
        // Просто отображаем страницу
        if (lstPost.SelectedItem == null)
            return;
        
        // Выбираем целевую страницу при AutoPostBack списка
        text = lstPost.SelectedItem.Text;
        if (String.Compare(text, "Студент", true) == 0)
            btnPostBackUrl.PostBackUrl = "~/CrossPageStudent.aspx";
        else 
            btnPostBackUrl.PostBackUrl = "~/CrossPageLecturer.aspx";
    }
    
    protected void btn_Click(object sender, EventArgs e)
    {
        // Контролируем наличие выбора при щелчках на кнопках
        if (lstPost.SelectedItem == null)
        {
            String mess = "<script>alert('Выберите сотрудника')</" + "script>";
            this.Response.Write(mess);
            return;
        }
    
        text = lstPost.SelectedItem.Text;
        
        // Различаем кнопки
        Button btn = (Button)sender;
        if (String.Compare(btn.Text, "PostBackUrl", true) != 0)
        {
            if (String.Compare(text, "Студент", true) == 0)
                this.Server.Transfer("~/CrossPageStudent.aspx", true);
            else
                this.Server.Transfer("~/CrossPageLecturer.aspx", true);
        }
    }
</script>
    
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <div style="text-align: center">
            <asp:Label ID="lblTitle" runat="server" 
                       Text="Это первая страница-источник"></asp:Label>
            <br />
            <br />
            Фамилия:&nbsp;
            <asp:TextBox ID="txtName" runat="server">Иванов</asp:TextBox>&nbsp;
            <asp:ListBox ID="lstPost" runat="server" Height="45px" AutoPostBack="True">
                <asp:ListItem>Студент</asp:ListItem>
                <asp:ListItem>Доцент</asp:ListItem>
            </asp:ListBox>
            <br />
            <br />
            <asp:Button ID="btnPostBackUrl" runat="server" 
                        Text="PostBackUrl" OnClick="btn_Click" />&nbsp;
            <asp:Button ID="btnServerTransfer" runat="server" OnClick="btn_Click"
                Text="Server.Transfer()" />
            <br />
            <br />
            <asp:HyperLink ID="HyperLink1" runat="server" 
                           NavigateUrl="SourcePostBack2.aspx">
            На вторую страницу-источник</asp:HyperLink>
        </div>
    </form>
</body>
</html>
Листинг 35.18. Полный код страницы SourcePostBack1.aspx
  • Получите копию страницы SourcePostBack1.aspx под именем SourcePostBack2.aspx и скорректируйте в ней элементы lblTitle и HyperLink1, выделенные в листинге страницы SourcePostBack1.aspx (" вторая " и " SourcePostBack1.aspx ")
  • Создайте новую страницу с именем CrossPageStudent.aspx без файла отделенного кода, в которой блок скриптов заполните так
<%@ Page Language="C#" %>
    
<script runat="server">
    
    // Поля, заполняемые данными вызвавшей страницы
    String name, title, className;
    
    // Наша функция, использующая доступ с помощью механизма отражения типов
    void GetInfoPreviousPage()
    {
        // Получаем тип запускающей страницы для
        // динамического вызова членов представляющего класса
        System.Type typePreviousPage = this.PreviousPage.GetType();
        // Находим общедоступный метод класса 
        System.Reflection.MethodInfo methodGetName =
            typePreviousPage.GetMethod("GetName");
        // Выполняем метод предыдущей страницы
        name = methodGetName.Invoke(this.PreviousPage, null).ToString();
        // Находим общедоступное свойство класса 
        System.Reflection.PropertyInfo propertyGetTitle =
            typePreviousPage.GetProperty("GetTitle");
        // Выполняем свойство предыдущей страницы
        title = propertyGetTitle.GetGetMethod().
            Invoke(this.PreviousPage, null).ToString();
        // Извлекаем имя класса предыдущей страницы
        className = this.PreviousPage.GetType().Name;
    }
    
    protected void Page_Load(object sender, EventArgs e)
    {
        // Получаем информацию с предыдущей страницы
        GetInfoPreviousPage();
    
        Label label = new Label();
        form1.Controls.Add(label);
        string text = "<h1 style='text-align: center; color: Red'>";
    
        if (this.PreviousPage == null)
        {
            text += "Страница запущена напрямую</h1>";
            label.Text = text;
            return;
        }
    
        text += "Методом межстраничной обратной отсылки получены данные</h1>";
        text += "<ul>";
        text += "<li>Заголовок: " + title + "</li>";
        
        if (String.Compare(className, "SourcePostBack1_aspx", true) == 0)
            text += "<li>Данные прислала первая страница</li>";
        else if (String.Compare(className, "SourcePostBack2_aspx", true) == 0)
            text += "<li>Данные прислала вторая страница</li>";
        text += "<li>Фамилия студента: " + name + "</li>";
        
        if (this.PreviousPage.IsCrossPagePostBack)
            text += "<li>Обратная отсылка инициирована свойством PostBackUrl</li>";
        else
            text += "<li>Обратная отсылка инициирована методом Server.Transfer()</li>";
        text += "</ul>";
        label.Text = text;
    }
</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.19. Полный код страницы-приемника данных CrossPageStudent.aspx
  • Получите копию страницы CrossPageStudent.aspx под именем CrossPageLecturer.aspx

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

  • Разберитесь с кодом отсылающей и принимающей сторон и испытайте их работу

Вариант результата на принимающей странице будет примерно таким


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