Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений .NET Framework Введение в инфраструктуру Navigation for ASP.NET Web Forms RSS

Введение в инфраструктуру Navigation for ASP.NET Web Forms

Текущий рейтинг: 0 (проголосовало 0)
 Посетителей: 951 | Просмотров: 3128 (сегодня 0)  Шрифт: - +
Инфраструктура Navigation for ASP.NET Web Forms — проект с открытым исходным кодом, размещенный на navigation.codeplex.com, — позволяет писать код Web Forms с охватом модульными тестами и соблюдением принципов DRY (Don’t Repeat Yourself), от чего разработчики приложений ASP.NET MVC могли бы позеленеть от зависти.

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

Механизм связывания данных с элементами управления ObjectDataSource существует примерно со времен Visual Studio 2005, обеспечивая более четкое написание и охват модульными тестами отделенного кода и кода для извлечения данных, но в нем были некоторые проблемы, препятствующие его широкому принятию, в частности генерация исключений была единственным способом передачи ошибок проверки данных из прикладной логики в UI.

Подавляющее большинство усилий в разработке Web Forms для предстоящего выпуска новой версии Visual Studio было нацелено на совершенствование механизма связывания с данными, в том числе на то, чтобы предоставить концепции связывания модели из MVC для решения этих проблем. Например, введение состояния модели (model state) устраняет проблему с передачей ошибок проверки из прикладной логики в UI. Тем не менее два шипа в этом механизме, относящиеся к навигации и передаче данных, остались, и вы можете уколоться о них. Но эти шипы можно безболезненно срезать, используя инфраструктуру Navigation for ASP.NET Web Forms (с этого момента я буду называть ее для краткости просто инфраструктурой Navigation).

Первый шип — отсутствие абстракции для логики навигации, тогда как в MVC она инкапсулируется в возвращаемых типах метода контроллера. Это приводит к переадресации вызовов внутри методов, связывающих данные, что исключает их из модульного тестирования. Второй шип — тип параметра в ObjectDataSource определяет, откуда берется его значение, например QueryStringParameter всегда получает свои данные из строки запроса. Это не позволяет использовать один и тот же источник данных в разных навигационных контекстах без написания необходимой логики в отделенном коде.

Инфраструктура Navigation срезает эти шипы целостным подходом к навигации и передаче данных. Независимо от типа выполняемой навигации — будь то гиперссылка, обратная передача (postback), история AJAX или модульный тест — передаваемые данные всегда хранятся одинаково. В будущих статьях я покажу, как это ведет к опустошению файлов отделенного кода с полностью охватываемой модульными тестами логикой извлечения данных и навигации, а также к обеспечению дружественности к механизму оптимизации под поисковые системы (Search Engine Optimization, SEO) и избавлению одностраничных приложений от дублирования кода в ситуациях как с использованием JavaScript, так и без. В этой статье вы познакомитесь с инфраструктурой Navigation и увидите в действии некоторые базовые (но ключевые!) концепции на примере создания веб-приложения.

Приложение-пример

В качестве примера мы воспользуемся веб-приложением для опросов через Интернет (online survey). В опросе предлагается ответить всего на два вопроса и по окончании выводится сообщение «thank you» («благодарим вас»). Каждый вопрос представлен отдельной ASPX-страницей (Question1.aspx и Question2.aspx соответственно), а для сообщения «thank you» отведена своя страница — Thanks.aspx.

Первый вопрос звучит так: «Which ASP.NET technology are you currently using?» («Какую ASP.NET-технологию вы используете в настоящее время?»). Возможные ответы: «Web Forms» или «MVC». Поэтому я добавлю в Question1.aspx этот вопрос и кнопки-переключатели для ответов, «зашитых» в код:

<h1>Question 1</h1>
<h2>Which ASP.NET technology are you currently using?</h2>
<asp:RadioButtonList ID="Answer" runat="server">
  <asp:ListItem Text="Web Forms" Selected="True" />
  <asp:ListItem Text="MVC" />
</asp:RadioButtonList>

На второй вопрос — «Are you using the Navigation for ASP.NET Web Forms framework?» («Используете ли вы инфраструктуру Navigation for ASP.NET Web Forms») — можно ответить либо «Yes», либо «No», и разметка второй страницы аналогична первой.

Приступаем

Самый простой способ настройки веб-проекта опросов на использование инфраструктуры Navigation — установить ее с помощью NuGet Package Manager. Выполнив команду Install-Package Navigation из Package Manager Console, вы добавите необходимые ссылку и конфигурацию. Если вы не пользуетесь Visual Studio 2010, инструкции по установке вручную см. по ссылке navigation.codeplex.com/documentation.

Конфигурация Navigation

Инфраструктуру Navigation можно рассматривать как конечный автомат, где каждое состояние представляет страницу и навигация между страницами (состояниями) называется переходом (transition). Предопределенный набор состояний и переходов конфигурируется в файле StateInfo.config, создаваемым при установке через NuGet. Без этой конфигурации запуск приложения для опросов привел бы к генерации исключения.

Поскольку состояния фактически являются страницами, приложению для опросов нужны три состояния — по одному на каждую из трех страниц:

<state key="Question1" page="~/Question1.aspx">
</state>
<state key="Question2" page="~/Question2.aspx">
</state>
<state key="Thanks" page="~/Thanks.aspx">
</state>

С этого момента я буду ссылаться на разные состояния по их ключевым именам (Question1, Question2 и Thanks), а не именам страниц, которые они представляют.

Поскольку переходы описывают возможные навигации между состояниями, приложению для опросов требуется два перехода: один для навигации с Question1 в Question2 и другой — с Question2 в Thanks. Переход является потомком состояния, из которого выходит программа, и указывается через атрибут to на состояние, в которое входит программа:

<state key="Question1" page="~/Question1.aspx">
  <transition key="Next" to="Question2"/>
</state>
<state key="Question2" page="~/Question2.aspx">
  <transition key="Next" to="Thanks"/>
</state>
<state key="Thanks" page="~/Thanks.aspx">
</state>

Диалоги являются последним элементом конфигурации и представляют логическое группирование состояний. Приложению для опросов достаточно одного диалога, поскольку Question1, Question2 и Thanks — это фактически один путь навигации. Атрибут initial диалога должен указывать начальное состояние, т. е. Question1:

<dialog key="Survey" initial="Question1"
  path="~/Question1.aspx">
  <state key="Question1" page="~/Question1.aspx">
    <transition key="Next" to="Question2"/>
  </state>
  <state key="Question2" page="~/Question2.aspx">
    <transition key="Next" to="Thanks"/>
  </state>
  <state key="Thanks" page="~/Thanks.aspx">
  </state>
</dialog>

Вы заметите, что каждый диалог, состояние и переход имеет атрибут key. Я предпочел именовать ключи состояний по именам страниц, но это не обязательно. Но обратите внимание на то, что все ключи должны быть уникальны в пределах своего предка; например, у вас не может быть состояний одного уровня (sibling states) с одинаковым ключом.

Теперь Question1.aspx указан в качестве стартовой страницы, и приложение для опросов успешно запустится в состоянии Question1. Однако опрос останется в этом состоянии, так как переход к Question2 не определен.

Навигация

Различные виды навигации в Web Forms полезно разбить на две категории. К категории без обратных передач (non-postback) относятся случаи навигации, где управление переходит от одной ASPX-страницы другой и принимает форму гиперссылок, перенаправлений (redirects) или пересылок (transfers). В категорию с обратными передачами (postback) входят случаи навигации, где управление остается в рамках одной страницы и принимает форму обратных передач, запросов частичной страницы (partial page requests) или AJAX-истории (AJAX history). Вторую категорию мы рассмотрим в будущей статье, когда будем обсуждать шаблон одностраничного интерфейса (single-page interface). В этой статье я сосредоточусь на первом типе навигации.

Для перехода между страницами нужно сформировать URL. До Visual Studio 2008 единственным способом было конструирование URL вручную из «зашитых» имен ASPX-страниц; это приводило к жесткому связыванию между страницами и в свою очередь вызывало большие трудности в сопровождении приложений. Введение маршрутизации (routing) смягчило остроту этой проблемы — вместо имен страниц использовались имена конфигурируемых маршрутов. Однако тот факт, что маршрутизация вне веб-среды приводит к исключению — в сочетании с ее стойкостью к подделке, — делает ее врагом для модульного тестирования.

Инфраструктура Navigation сохраняет свободное связывание, обеспечиваемое маршрутизацией, и дружественна к модульному тестированию. Код в предыдущем разделе ссылается на сконфигурированные ключи диалогов и переходов; состояние, в которое осуществляется переход, определяется соответствующими атрибутами initial и to.

В приложении-примере для перехода от Question1 к Question2 можно использовать ключ перехода Next. Я добавлю кнопку Next в Question1.aspx и следующий код в сопоставленный с ней обработчик событий Click:

protected void Next_Click(object sender, EventArgs e)
{
  StateController.Navigate("Next");
}

Ключ, переданный в метод Navigate, сравнивается со сконфигурированными дочерними переходами состояния Question1, а затем отображается состояние, идентифицируемое атрибутом to, т. е. Question2. Я добавлю те же кнопку и обработчик в Question2.aspx. Если вы запустите приложение, то обнаружите, что теперь у вас появилась возможность переходить через три состояния, щелкая кнопки Next.

Вероятно, вы заметили, что второй вопрос относится исключительно к Web Forms и, как таковой, он не годится в том случае, если в качестве ответа на первый вопрос был выбран вариант «MVC». Поэтому в коде нужно изменить обработку этой ситуации и переходить от Question1 непосредственно к Thanks, минуя Question2.

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

<state key="Question1" page="~/Question1.aspx">
  <transition key="Next" to="Question2"/>
  <transition key="Next_MVC" to="Thanks"/>
</state>

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

if (Answer.SelectedValue != "MVC")
{
  StateController.Navigate("Next");
}
else
{
  StateController.Navigate("Next_MVC");
}

От опроса не будет особого толку, если у пользователя нет возможности менять ответы. На данный момент нет возможности вернуться к предыдущему вопросу (кроме кнопки перехода назад в браузере). Для обратной навигации вы могли решить, что нужно добавить два перехода под Thanks, указывающие на Question1 и Question2, и еще один — под Question2, указывающий на Question1. Хотя такой вариант сработал бы, это лишнее, так как обратная навигация поддерживается самой инфраструктурой Navigation.

Иерархическая навигация (breadcrumb navigation) — это набор ссылок, обеспечивающих доступ к каждой предыдущей странице, которую посещал пользователь, добираясь до текущей страницы. В Web Forms иерархическая навигация встроена в ее функциональность карт сайтов. Однако, поскольку карты сайтов представлены фиксированной навигационной структурой, для данной страницы эти переходы всегда одинаковы независимо от выбранного маршрута. Они не могут обрабатывать ситуации, аналогичные той, которую мы видим в приложении для опросов, где маршрут к Thanks иногда исключает Question2. Инфраструктура Navigation, отслеживая состояния, которые пользователь посещал в процессе навигации, формирует иерархическую трассировку реального маршрута.

Для демонстрации этого я добавлю гиперссылку в Question2.aspx, а в отделенном коде буду программным способом задавать ее свойство NavigateUrl, используя обратную навигацию. При этом должен передаваться параметр расстояния (distance), указывающий, на сколько состояний следует вернуться; значение 1 означает возврат к ближайшему предшественнику:

protected void Page_Load(object sender, EventArgs e)
{
  Question1.NavigateUrl =
    StateController.GetNavigationBackLink(1);
}

Если вы запустите приложение и выберете ответ «Web Forms» на первый вопрос, то увидите гиперссылку в Question2.aspx, которая позволяет вернуться к первому вопросу.

То же самое я сделаю в Thanks.aspx, хотя этот случай посложнее из-за необходимости двух гиперссылок — по одной на каждый вопрос. Но дело в том, что пользователь может не увидеть двух вопросов, выбрав «MVC» в качестве ответа на первый вопрос. Поэтому при создании гиперссылок нужно проверять количество предшествующих состояний (рис. 1).

Рис. 1. Динамическая обратная навигация

protected void Page_Load(object sender, EventArgs e)
{
  if (StateController.CanNavigateBack(2))
  {
    Question1.NavigateUrl =
      StateController.GetNavigationBackLink(2);
    Question2.NavigateUrl =
      StateController.GetNavigationBackLink(1);
  }
  else
  {
    Question1.NavigateUrl =
      StateController.GetNavigationBackLink(1);
    Question2.Visible = false;
  }
}

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

Передача данных

В Web Forms много способов передачи данных, равно как и способов навигации. В случае навигации без обратных передач, где управление передается от одной страницы другой (через гиперссылку, перенаправление или пересылку), можно использовать строку запроса или данные маршрута. При навигации с обратными передачами, где управление остается в рамках одной страницы (через обратную передачу, запрос частичной страницы или AJAX-историю), вероятными кандидатами являются значения элементов управления, состояние отображения (view state) или аргументы событий.

До Visual Studio 2005 файлы отделенного кода были сильно обременены обработкой таких передаваемых данных и разбухали из-за большого объема логики извлечения значений и преобразования типов. Нагрузка на эти файлы значительно сократилась с введением элементов управления «источник данных» и параметров select (провайдеров значений в следующей версии Visual Studio). Однако эти параметры select привязаны к определенному источнику данных, и они не могут динамически переключаться между источниками в зависимости от навигационного контекста. Например, они не способны получать свои значения от альтернативного элемента управления или из строки запроса. Обход этих ограничений вызывает утечку кода обратно в отделенный код, и вы вновь оказываетесь с раздутыми и неподдающимися тестированию файлами.

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

Я изменю опрос так, чтобы ответ на первый вопрос передавался состоянию Thanks, где он будет заново показан пользователю. Данные передаются при навигации через набор пар «ключ-значение» — NavigationData. Обработчик щелчка кнопки Next в Question1.aspx модифицируется для передачи ответа на первый вопрос в следующее состояние:

NavigationData data = new NavigationData();
data["technology"] = Answer.SelectedValue;
if (Answer.SelectedValue != "MVC")
{
  StateController.Navigate("Next", data);
}
else
{
  StateController.Navigate("Next_MVC", data);
}

Этот NavigationData, передаваемый при навигации, используется для инициализации данных состояния, которые становятся доступными следующему состоянию через свойство Data объекта StateContext. Я добавлю Label в Thanks.aspx и настрою его свойство Text для отображения переданного ответа:

Summary.Text = (string) StateContext.Data["technology"];

Запустив опрос, вы заметите, что информация сводки отображается, только когда ответ на первый вопрос — «MVC»; ответ «Web Forms» никогда не показывается. Это связано с тем, что NavigationData доступен только в следующем состоянии, но не состояниям, достигаемым в результате последующей навигации. Поэтому ответ «Web Forms» присутствует в данных состояния Question2, но недоступен к моменту достижения Thanks. Один из способов решения этой проблемы — такое изменение Question2.aspx, чтобы она ретранслировала ответ на первый вопрос, т. е. извлекала ответ из своих данных состояния и передавала его в Thanks при навигации:

NavigationData data = new NavigationData();
data["technology"] = StateContext.Data["technology"];
StateController.Navigate("Next", data);

Этот подход не идеален, потому что он приводит к связыванию Question1 и Question2, заставляя последнее состояние определять данные, передаваемые первым состоянием. Например, новый вопрос вставить между первым и вторым вопросами не удастся без соответствующего изменения Question2.aspx. Реализация с заделом на будущее должна включать создание нового NavigationData, содержащего все данные состояния Question2; для этого в конструктор NavigationData передается true:

NavigationData data = new NavigationData(true);
StateController.Navigate("Next", data);

Другое ключевое различие между данными состояния и строкой запроса или данными маршрутов состоит в том, что данные состояния не ограничивают вас передачей только строк. Вместо передачи ответа в виде строки, как делалось для Question1, я буду передавать из Question2 в Thanks значение типа bool, где true соответствует «Yes»:

NavigationData data = new NavigationData(true);
data["navigation"] = Answer.SelectedValue ==
  "Yes" ? true : false;
StateController.Navigate("Next", data);

Как видите, тип данных сохраняется при извлечении из данных состояния Thanks:

Summary.Text = (string) StateContext.Data["technology"];
if (StateContext.Data["navigation"] != null)
{
  Summary.Text += ", " +
    (bool) StateContext.Data["navigation"];
}

Опрос почти готов, осталось решить одну задачу: ответы на вопросы не сохраняются при использовании гиперссылок для обратной навигации. Например, при возврате в Question1 от Thanks контекст теряется, поэтому всегда выбирается исходная кнопка-переключатель «Web Forms» независимо от данного ранее ответа.

В предыдущем разделе вы видели преимущества обратной навигации по сравнению с использованием статической иерархии карты сайта. Другое ограничение такой иерархии, генерируемой картой сайта, заключается в том, что она не несет никаких данных. То есть, если вы следуете этой иерархии, вы можете потерять контекстную информацию. Например, она не может передать выбранный ранее ответ «MVC» при возврате в Question1 из Thanks. Инфраструктура Navigation, отслеживая данные состояния, которые сопоставляются с состояниями по мере их посещения при навигации, формирует контекстно-зависимую трассировку иерархии. При обратной навигации эти данные состояния восстанавливаются, позволяя воссоздавать страницу точно в том виде, в каком она была до этого момента.

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

StateContext.Data["answer"] = Answer.SelectedValue;

Теперь, когда Question1 или Question2 посещаются повторно, данные состояния будут содержать ранее выбранный ответ. Ну а дальше вы просто извлекаете этот ответ в методе Page_Load и программным способом выбираете соответствующую кнопку-переключатель:

protected void Page_Load(object sender, EventArgs e)
{
  if (!Page.IsPostBack)
  {
    if (StateContext.Data["answer"] != null)
    {
      Answer.SelectedValue =
        (string)StateContext.Data["answer"];
    }
  }
}

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

Пусть MVC завидует

Я уже говорил, что инфраструктура Navigation позволяет писать такой код для Web Forms, от которого разработчики MVC-приложений позеленеют от зависти. После столь смелого заявления вы, возможно, чувствуете себя слегка обманутым, изучив приложение-пример для опросов. Но, пожалуйста, не расстраивайтесь; это было не более чем введение в базовые концепции. В будущих статьях я сосредоточусь на архитектурной целостности, уделяя особое внимание модульному тестированию и принципам DRY.

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

В третьей статье я создам одностраничное приложение, дружественное к механизму SEO. В нем будет применяться ASP.NET AJAX при включении JavaScript, и оно будет корректно сокращать свою функциональность при отключении JavaScript; при этом в обоих случаях будут использоваться одни и те же связанные с данными методы. И вновь добиться такого в MVC-приложении весьма непросто.

Если вы заинтересовались и вам не терпится опробовать более «продвинутую» функциональность, скачайте полную документацию и примеры кода с navigation.codeplex.com.

Автор: Грэм Мендик  •  Иcточник: MSDN Magazine  •  Опубликована: 14.11.2012
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   ASP.NET.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.