Навигация по сайту в ASP.NET 2.0

ОГЛАВЛЕНИЕ

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

Например, такой сайт ,как Amazon.com, разделен на несколько различных секций, каждая из которых предназначена для отдельного семейства (или категории) товаров, таких как книги (Books), электронные устройства (Electronics), компьютеры (Computers), DVD, и т.д. Каждая данная секция может состоять из подразделов. Раздел "Книги" (Books) разделен на такие категории, как "Книги на CD дисках" (Books on CD), романы (Novels), исторические (History), мелодрамы (Romance), и т.д. Обычно данные логические структуры образуются согласно иерархии типов. Следующее изображение демонстрирует сокращенную версию карты сайта Amazon.com.

 

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

До появления ASP.NET 2.0 разработчики обычно  находили свои собственные пути  решения такой проблемы, как навигация по сайту. В настоящее время  использование ASP.NET 2.0  позволит вам с легкостью определить структуру сайта и реализовать ее при помощи обычных элементов интерфейса навигации. В данной статье мы рассмотрим особенности навигации по сайту при помощи ASP.NET 2.0. Читайте далее, чтобы узнать больше об этом!



Навигация по сайту в ASP.NET 1.x

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

  1. Решить, как организовать в карте сайта информацию о структуре сайта
  2. Реализовать элементы интерфейса навигации
Что касается первого препятствия, то разработчикам необходимо было решить, как смоделировать структуру сайта. Они могли хранить информацию в XML файле, либо добавить таблицу базы данных, которая содержала в себе различные секции сайта и их зависимость. Для тех сайтов, которые поддерживают учетные записи, на сайте могли быть разделы, которые были доступны только определенным пользователям. Более того, многоязычным сайтам требуется наличие какого-либо перевода различных секций.

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

В общем, реализация навигации по сайту в ASP.NET 1.x не была столь сложной задачей, но  все же это было одним из препятствий на пути создания сайта. Более того, поскольку не существовало никакой встроенной поддержки интерфейса навигации, каждый разработчик создавал свое собственное решение данной проблемы, что усложняло работу новым разработчикам проекта, которым приходилось разбираться и вникать в логику интерфейса навигации.



Навигации по сайту в ASP.NET 2.0

Реализация интерфейса навигации по сайту в ASP.NET 2.0 становится легкой задачей благодаря особенностям данной технологии. ASP.NET предоставляет программную библиотеку API, которая позволяет осуществлять запросы по сайту. ASP.NET не требует определенного формата для определения карты сайта, хотя по умолчанию предполагает использование XML файла. Детали настройки карты сайта могут быть определены вручную, так как функциональность навигации в ASP.NET 2.0 использует модель провайдеров (Provider model). Модель провайдеров позволяет разработчикам настроить работу выполняемую в пределах конкретной подсистемы ASP.NET, при этом не изменяя API.

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

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

  • SiteMapPath - предоставляет иерархическую навигацию, отображая положение пользователя относительно структуры сайта. Например, при посещении раздела "Романы" (Novels) на сайте Amazon.com будет отображено что-то типа: Home > Books > Novels.
  • TreeView - отображает структуру сайта в иерархическом дереве.
  • Menu - отображает структуру сайта при помощи меню.
    При отображении интерфейса навигации сайта элементы TreeView и Menu используют элемент управления SiteMapDataSource для того, чтобы считывать содержимое карты сайта.
На самом деле, данные элементы управления вызывают API интерфейса навигации ASP.NET 2.0. Поскольку интерфейс навигации реализован при помощи модели провайдеров, элементы управления не "обращают внимания" на внутренние детали и на то, как организована карта сайта. То есть независимо от того, используете ли вы карту сайта по умолчанию, или же создаете свою логику карты, элементы управления навигацией могут быть использованы. (Если вы хотите использовать собственную карту сайта, то вам нужно будет создать класс, который обеспечит ее соответствующими методами и свойствами для работы с картой сайта.)



Построение карты сайта

Карта сайта состоит из набора связанных между собой объектов SiteMapNode. Эти объекты формируют иерархию (как показано в начале данной статьи). Иерархия содержит одиночные корневые узлы, которые не имеют родительского узла. Каждый узел в иерархии представляет собой логический раздел веб-сайта. Каждый раздел может иметь заголовок, указатель ресурса (URL), описание и т.д., которые моделируются свойствами класса SiteMapNodes(Title, Url, Description, и т.д.).

Иерархия объектов SiteMapNodes представляется в таком виде, в котором она отображена в памяти в то время, когда она просматривается API библиотекой интерфейса навигации сайта. Тем не менее, данная карта сайта должна каким-то образом быть физически организована, например, при помощи XML файла  или  в таблице базы данных. По умолчанию, ASP.NET 2.0 предоставляет стандартное решение в виде реализации карты сайта при помощи XML файла. Чтобы использовать данную технику, вам необходимо в корневом каталоге вашего веб-приложения, названного Web.sitemap,  создать XML файл, который будет иметь следующую структуру:

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0">
  <siteMapNode attributes="">
    <siteMapNode attributes="">
      <siteMapNode attributes="">
        ...
      </siteMapNode>
      <siteMapNode attributes="" />
      ...
      <siteMapNode attributes="" />
    </siteMapNode>
  </siteMap>
Создание файла Web.sitemap
С момента появления Visual Studio 2005 вы можете с легкостью создать данный файл карты сайта посредством выбора опции Add New Item при правом клике на сайте в проводнике Solution Explorer, выбрав затем иконку Site Map ("Карта сайта"). Удостоверьтесь, что название файла осталось Web.sitemap. Созданный файл будет иметь несколько XML элементов <siteMapNode>, похожих на те, что мы рассмотрели выше.

Элементы <siteMapNode> могут обладать несколькими атрибутами. Чаще всего встречаются следующие:

  • title - определяет заголовок раздела
  • url - определяет указатель ресурса раздела (URL); не является обязательным, но если он указан, каждый указатель карты сайта должен быть уникальным
  • description - (не обязательно указывать) отображает описание раздела; используется в атрибуте alt обработанных элементов управления интерфейса навигации
Элементы <siteMapNode> могут быть вложены; тем не менее карта сайта должна содержать корневой элемент <siteMapNode>. То есть узел <siteMap> должен обладать единственным потомком <siteMapNode>.

Следующая карта сайта демонстрирует пример структуры сайта Amazon.com, рассмотренной в начале данной статьи. (Данный файл, наряду со всем интерфейсом навигации веб-сайта, вы можете загрузить в конце данной статьи ...)

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <siteMapNode url="~/Default.aspx" title="Home">
    <siteMapNode url="~/Books/Default.aspx" title="Books">
      <siteMapNode url="~/Books/Novels.aspx" title="Novels" />
      <siteMapNode url="~/Books/History.aspx" title="History" />
      <siteMapNode url="~/Books/Romance.aspx" title="Romance" />
    </siteMapNode>
    <siteMapNode url="~/Electronics/Default.aspx" title="Electronics" />
    <siteMapNode url="~/DVDs/Default.aspx" title="DVDs" />
    <siteMapNode url="~/Computers/Default.aspx" title="Computers" />
  </siteMapNode>
</siteMap>



Отображение карты сайта при помощи элементов управления навигации

Теперь, когда мы определили карту сайта, мы готовы к выводу данной информации при помощи страницы ASP.NET. Как говорилось выше, существует три встроенных элемента управления навигации: SiteMapPath, TreeView и Menu. Использование данных элементов не представляет большого труда - просто перетащите их на ASP.NET страницу и настройте свойства для того, чтобы настроить соответствующий сценарий диалога с пользователем.

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

  • Шапка (верхняя часть) - здесь отображается заголовок сайта ("Welcome to my Website!")
  • Левая область - здесь расположен элемент управления TreeView, который отображает все содержимое карты сайта. Это позволит пользователю быстро переместиться в определенную область сайта.
  • Основная область - главная область будет включать уникальное содержимое каждой страницы, использующей мастер-страницу. (Заметьте элемент управления ContentPlaceHolder в главной области.)  В дополнение, элемент SiteMapPath также добавлен в верхнюю часть главной страницы, предоставляя иерархическую навигацию и отображая пользователям, где они находятся на сайте.

Чтобы добавить SiteMapPath на главную страницу, я просто-напросто перетаскиваю и бросаю элемент управления SiteMapPath из Toolbox на мастер-страницу. При добавлении элемента управления TreeView (либо Menu) вам необходимо сначала добавить на страницу элемент управления SiteMapDataSource; далее добавьте TreeView (либо Menu) и установите его свойство DataSourceID в значении ID элемента управления SiteMapDataSource (это может быть осуществлено посредством использования смарт-тэга TreeView). Элемент управления SiteMapDataSource осуществляет запрос по карте сайта, используя API интерфейса навигации, и предоставляет полную структуру карты сайта элементу TreeView (либо Menu).

Следующее изображение показывает веб-сайт, просмотренный в веб-браузере. Заметьте, что слева присутствует элемент управления TreeView, перечисляющий все содержимое карты сайта. Нажатие на любой узел в элементе управления TreeView переносит пользователя к соответствующему разделу. Элемент управления SiteMapPath в верхней части основной области демонстрирует пользователю его текущее положение на сайте (т.е., Home > Books > Novels).






Карта сайта

Вид данной структуры сайта, используемой в примерах, показан в следующем изображении


Программная работа с картой сайта

Карта сайта является набором взаимосвязанных узлов. Каждый узел карты сайта обычно содержит заголовок, указатель ресурса (URL) и описание. Изображение, показанное выше, является примером карты сайта, где каждый блок отражает определенный узел карты сайта. ASP.NET не требует определенного формата карты сайта, хотя, по умолчанию, предлагается использовать XML файл. (особенности использования XML файла мы исследовали в первой части.)

ASP.NET предоставляет класс, названный SiteMap , который подразумевает программный (только для чтения (read-only)) доступ к карте сайта. Данный класс характеризуется  двумя элементами управления, которые мы рассмотрим в данной статье:

  • SiteMapPath - обрабатывает иерархическую навигацию, основываясь на посещенной странице и ее расположении в структуре сайта. В частности, SiteMapPath начинает обработку с узла, возвращенного свойством SiteMap.CurrentNode, и продвигается вверх по иерархии, пока не достигнет корня.
  • SiteMapDataSource - данный элемент управления создает иерархический источник данных, который отражает структуру карты сайта. Для того чтобы отобразить информацию карты сайта в других элементах управления, таких как TreeView или Menu, элементы управления не осуществляют запрос к карте сайта напрямую; вместо этого они привязаны к конкретному элементу SiteMapDataSource, который обрабатывает чтение в структуре карты сайта. (Мы детально рассмотрим элемент управления SiteMapDataSource в следующих статьях.)

Данный класс SiteMap обладает двумя родственными свойствами: RootNode и CurrentNode. Оба эти свойства возвращают экземпляры SiteMapNode. Класс SiteMapNode является узлом, определенным в карте сайта, и обладает свойствами, описывающими узел - Title, Url, Description, а также свойствами, которые позволяют программно перемещаться по иерархии - ParentNode, ChildNodes, NextSibling, PreviousSibling, и т.д..

Вы можете использовать класс SiteMap в ваших собственных страницах ASP.NET. Для примера: мы можем вывести ссылки "Следующая" (Next), "Предыдущая" (Previous) и "Вверх" (Up) на каждой странице, путем добавления трех элементов управления HyperLink на мастер-страницу сайта, наряду с небольшим куском кода, который проверяет на присутствие NextSibling, PreviousSibling или ParentNode в CurrentNode. В частности, вы добавляете следующую разметку в вашу мастер-страницу:

<asp:HyperLink ID="lnkPrev" runat="server">Prev</asp:HyperLink>
<asp:HyperLink ID="lnkUp" runat="server">Up</asp:HyperLink>
<asp:HyperLink ID="lnkNext" runat="server">Next</asp:HyperLink>

Обработчик события Page_Load мастер-страницы должен выглядеть так:

        If SiteMap.CurrentNode IsNot Nothing Then     'Set the next/previous/up links 
            If SiteMap.CurrentNode.PreviousSibling IsNot Nothing Then
                lnkPrev.NavigateUrl = SiteMap.CurrentNode.PreviousSibling.Url
                lnkPrev.Text = "&lt; Prev (" & SiteMap.CurrentNode.PreviousSibling.Title & ")"
            Else : lnkPrev.NavigateUrl = String.Empty
                lnkPrev.Text = "&lt; Prev"
            End If
            If SiteMap.CurrentNode.ParentNode IsNot Nothing Then
                lnkUp.NavigateUrl = SiteMap.CurrentNode.ParentNode.Url
                lnkUp.Text = "Up (" & SiteMap.CurrentNode.ParentNode.Title & ")"
            Else : lnkUp.NavigateUrl = String.Empty
                lnkUp.Text = "Up"
            End If
            If SiteMap.CurrentNode.NextSibling IsNot Nothing Then
                lnkNext.NavigateUrl = SiteMap.CurrentNode.NextSibling.Url
                lnkNext.Text = "(" & SiteMap.CurrentNode.NextSibling.Title & ") Next &gt;"
            Else : lnkNext.NavigateUrl = String.Empty
                lnkNext.Text = "Next"
            End If
        End If

Это добавит три гиперссылки (Next, Up, и Previous) на каждую страницу, унаследовавшую мастер-страницу.




Отображаем иерархическую навигацию при помощи элемента управления SiteMapPath

Элемент управления SiteMapPath отображает иерархическую навигацию, оповещая пользователя об их расположении на сайте. Результат элемента управления SiteMapPath определяется тремя факторами:

  • Структурой сайта, определенной картой сайта,
  • Посещаемой страницей
  • Значением свойств элемента управления SiteMapPath

Когда посещается страница с элементом управления SiteMapPath, данный элемент пытается сопоставить страничный URL со значением указателя ресурса узла сайта, определенного в карте сайта. Если нет совпадений - элемент продвигается вверх по структуре к корню, создавая следующее: RootNode > ParentNode > ... > ParentNode > CurrentNode. Здесь CurrentNode является заголовком узла карты сайта, который сравнивается с указателем ресурса (URL) запроса текущей страницы; RootNode и ParentNodes обрабатываются как гиперссылки в том случае, если узел карты сайта обладает значением URL. Элемент управления SiteMapPath на странице History раздела Books (Books/History.aspx) обрабатывается как Home > Books > History, при этом Home и Books обрабатываются как ссылки на Default.aspx и Books/Default.aspx соответственно. Посещение страницы Books/Default.aspx, SiteMapPath обрабатывает просто как Home > Books.

Результат работы SiteMapPath зависит от карты сайта и посещенной страницы. Результат работы SiteMapPath может также быть настроен при помощи свойств элемента. Существуют три стандартных форматирующих свойства - BackColor, Font, ForeColor, - а также и другие настройки SiteMapPath, такие как:

  • PathDirection - может иметь одно или два значения - RootToCurrent (по умолчанию) или CurrentToRoot. При помощи RootToCurrent иерархическая навигация на странице History раздела Books обрабатывается как Home > Books > History; при помощи CurrentToRoot результатом будет History > Books > Home.
  • PathSeparator - указывает строку, используемую для отделения каждого узла в иерархической навигации; по умолчанию >
  • RenderCurrentNodeAsLink - логическое свойство (Boolean), которое указывает на то, стоит ли обрабатывать CurrentNode как ссылку; по умолчанию - False.
  • ParentLevelsDisplayed - целочисленное значение которое может быть установлено в предел отображаемой иерархии. По умолчанию, данное свойство установлено в значение -1, тем самым означая, что не существует предела; установка значения в 1 на странице History в результате даст обработку иерархии Books > History. Home не включается, так как элемент управления SiteMapPath проходит максимум через один родительский уровень - от History к Book.
  • ShowToolTips - если узел карты сайта имеет значение описания (description), описание отображается как подсказка для каждого узла иерархии в случае, если данное свойство установлено в True (по умолчанию).

Также существуют свойства стиля для настройки BackColor, Font, ForeColor и т.д., для различных частей элемента SiteMapPath. Свойство NodeStyle может быть использовано для настройки внешнего вида узлов и иерархической навигации; RootNodeStyle и CurrentNodeStyle могут быть использованы для последующей настройки первого и последнего узлов в иерархии. Часто простейшим и наиболее практичным методом форматировать элемент управления SiteMapPath может быть использование его мастера Auto Format, который можно применить, используя смарт-тэг элемента управления.

Настраиваем обработанный результат при помощи шаблонов

Элемент SiteMapPath содержит четыре шаблона, которые позволяют последующую настройку результата обработки. Шаблоны позволяют смешивать HTML разметку , веб-элементы управления и синтаксис привязки данных (DataBinding); если вы раньше использовали элементы управления DataList или Repeater, то вы уже должны быть знакомы с шаблонами. Шаблоны в ASP.NET 2.0 по существу являются такими же, как и шаблоны в ASP.NET 1.x, за исключением того, что ASP.NET 2.0 использует более новый и насыщенный синтаксис выражений привязки данных. Например, в ASP.NET 1.x вам нужно было использовать синтаксис <%# DataBinder.Eval(Container.DataItem, PropertyName) %> для того, чтобы получить значение из колонки. В ASP.NET 2.0 данный старый синтаксис работает, но вы также можете использовать и более короткий вариант, <%# Eval(PropertyName) %>.

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

Например, если вы хотели обработать всего лишь корневой узел, принадлежащий SiteMapPath, как LinkButton, вы можете добавить <RootNodeTemplate> к элементу SiteMapPath вместе со следующей разметкой:

<asp:SiteMapPath ID="SiteMapPath1" runat="server">
  <RootNodeTemplate>
    <asp:LinkButton ID="LinkButton1" runat="server" Text='<%# Eval("title") %>'    
        CommandArgument='<%# Eval("url") %>'
        OnCommand="LinkButton1_Command">     
    </asp:LinkButton>
  </RootNodeTemplate>
</asp:SiteMapPath>

Данная разметка добавит элемент LinkButton к SiteMapPath, чье свойство Text назначено соответствующему свойству Title в SiteMapNode. При нажатии на LinkButton вызывается постбэк и событие Command элемента, тем самым вызывая обработчик события LinkButton1_Command. Свойство Url в SiteMapNode передается данному обработчику события при помощи свойства CommandArgument. В обработчике события вы можете выполнить все то, что было необходимо сделать при обработке на сервере и затем, после этого, перенести пользователя на страницу, запрашиваемую путем Response.Redirect(e.CommandArgument).


Ограничение доступа к пунктам карты сайта

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

К счастью, навигация в ASP.NET 2.0 предоставляет такую услугу, как ограничение прав (security trimming).  При получении информации о карте сайта при настроеном ограничении в правах (security trimming) пользователю доступны только те узлы карты сайта, которые ему разрешено посещать. Это означает, что элементы TreeView либо Menu на сайте будут содержать только те, которые доступны данному пользователю. Читайте далее, чтобы узнать больше об этом!

Начнем с настройки Membership и ролей (опционально) в ASP.NET 2.0

Поскольку ограничение прав в навигации сайта  лежит в основе построения карты сайта на пользователе, который посещает страницу, а также на настройках авторизации, указанных в карте сайта для страниц, то перед тем как исследовать функцию ограничения прав (security trimming), вам нужно будет настроить сайт на использование такой службы в ASP.NET 2.0,  как  Membership.  (Вы также можете использовать роли в вашем сайте и создать систему авторизации, основанную на ролях, но это не является обязательным при изучении принципа ограничения прав (security trimming)). Тема настройки сайта на использование  Membership и ролей (Roles) выходит за рамки данной статьи.

Приложение в конце данной статьи включает в себя пример ограничения прав (security trimming) по карте для сайта, который реализует службу  Membership и ролей (Roles), и который вы можете использовать в том случае, если не хотите вручную настраивать свойства Membership и ролей на свежем веб-сайте. В частности, веб-сайт, приложенный к данной статье, содержит две роли, администратор (Administrator) и тестер (Tester), с четырьмя пользователями:

  • Superman - в качестве администратора и тестера
  • Admin - в качестве администратора
  • Mr. Tester - в качестве тестера
  • Average User - не обладающий ни одной ролью

Более того, в проекте присутствуют три папки - Admin, Tester и AuthUsersOnly. Первые два каталога были настроены таким образом, что к ним доступ имели только пользователи, обладающие ролями администратора (Administrator) и тестера (Tester), соответственно. Только пользователи с определенными правами имеют доступ к каталогу AuthUsersOnly.


Настройка навигации по сайту, используя ограничения прав (security trimming)

По умолчанию навигация по сайту не использует ограничения прав (security trimming). Вне зависимости от типа пользователя, посещающего сайт, а также от указанных правил авторизации, каждый пользователь видит все части карты сайта при просмотре информации с помощью элементов управления TreeView либо Menu. Путем установления ограничений система навигации по сайту автоматически ограничивает результаты, основываясь на текущем авторизированном пользователе и на ограничениях в доступе, на которые ссылаются элементы <siteMapNode> в карте сайта.

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

<siteMap defaultProvider="XmlSiteMapProvider" enabled="true">
  <providers>
    <add name="XmlSiteMapProvider"
         description="Default SiteMap provider."
         type="System.Web.XmlSiteMapProvider"
         siteMapFile="siteMapFileName"
         securityTrimmingEnabled="true" />
  </providers>
</siteMap>

Модель провайдеров (provider model) предоставляет разработчикам определенный публичный (public) интерфейс прикладных программ (API), но позволяет настроить внутреннюю обработку при необходимости. По умолчанию навигация по сайту использует XmlSiteMapProvider, которая содержит информацию о карте сайта из XML-файла Web.sitemap. Вы можете изменить используемый провайдер, либо изменить настройки провайдера, установленного по умолчанию, при помощи файла through the Web.config.

Чтобы изменить настройки провайдера, установленного по умолчанию, просто добавьте нового провайдера, который будет использовать тот же тип, как и стандартный провайдер (System.Web.XmlSiteMapProvider), устанавливая настройки по необходимости. Блок кода, показанный выше, демонстрирует способ настройки двух параметров XmlSiteMapProvider:

  • Параметр siteMapFile содержит название файла карты сайта, используемое провайдером; по умолчанию данное значение равно Web.sitemap. При желании вы можете настроить название файла. Я советую вам проверять файл, чтобы  быть уверенным, что название файла оканчивается расширением .sitemap, поскольку данное расширение защищено движком ASP.NET, тем самым защищая файл от просмотра пользователями.
  • Параметр securityTrimmingEnabledуказывает на то, использовано ли ограничение прав (security trimming) или нет. Установите данный параметр в значении "Истина" (True), если требуется использование ограничений прав.

И это все! Применив одну настройку, система навигации по сайту станет настолько смышленой, что будет отображать разделы соответственно настройкам авторизации, указанным для ссылок на ресурсы (URL) в элементах <siteMapNode>, а также от авторизированного пользователя. Следующее изображение показывает элемент TreeView, отображенный при посещении анонимного пользователя (тот, кто еще не авторизовался), а также при посещении обычного пользователя (Average User) и администратора (Admin user). Анонимный пользователь увидит (все) две ссылки - Home и My Blog. Обычный пользователь (Average User) увидит дополнительную ссылку - Auth Users, которая не была отображена анонимному пользователю, поскольку данная ссылка на ресурс (~/AuthUsers/Default.aspx) настроена таким образом, что только авторизированные пользователи имеют к ней доступ. Администратору (Admin user) доступна еще одна дополнительная ссылка, поскольку он обладает ролью администратора (Administrator role) и ссылка на ресурс, указанная в узле Admin карты сайта (~/Admin/Default.aspx), настроена таким образом, что только администраторы имеют к ней доступ.

The TreeView When Visited by an Anonymous Visitor

The TreeView When Visited by Average User The TreeView When Visited by Admin




Избегаем ограничения прав (Security Trimmings) при помощи атрибута roles

Бывают случаи, когда вам нужно будет явно указать в ограничениях прав то, что не стоит ограничивать доступ к карте сайта для конкретной роли или группы ролей. Например, если ваша карта сайта содержит ссылку на внешний ресурс, система навигации сайта не сможет определить правила авторизации для внешнего ресурса. Тем самым она ставит ограничения на все узлы для всех пользователей. То есть, если вы используете ограничения прав (security trimming) и используете карту сайта с ссылкой на внешний ресурс (например <siteMapNode url="http://www.scottonwriting.net/sowBlog/" title="My Blog" />), то ни один из пользователей не увидит этого в элементах TreeView либо Menu. Более того, вам наверняка понадобится настроить систему навигации таким образом, чтобы она отображала данный узел для пользователей, обладающих ролями администратора и тестера (либо для всех пользователей, независимо от их роли.)

Аналогично вам может понадобиться отобразить пользователям узлы локальной карты сайта, даже если у них нет доступа к данным ресурсам. Например, пользователь, который посещает карту сайта и ему еще необходимо авторизоваться, скорее всего, не увидит Admin link в TreeView. Тем не менее мы все же хотим отобразить ее. Нажатие на ссылку вызовет перенос пользователя на страницу ~/Admin/Default.aspx, где система различит авторизированных пользователей от неавторизированных. То есть они будут перенесены на страницу авторизации. После авторизации они будут автоматически перенесены обратно на страницу администрирования (Admin). Если же они обладают ролью администратора (Administrator role), то им будет разрешен доступ к разделу администратора (Admin), в противном случае они будут перенесены обратно на страницу авторизации.

Чтобы устанавливать ограничение по правам на определенные узлы сайта , используйте атрибут roles в соответствующем элементе <siteMapNode>. (Заметьте: данная настройка не будет действовать на потомков элемента <siteMapNode>; то есть вам необходимо напрямую настроить данный атрибут в элементах <siteMapNode>, в которых вы хотели бы явно указать дополнительные роли, которым необходимо видеть данный узел.) Атрибут roles может содержать одно название роли, список имен, отделенных запятой, либо звезду (*) для выбора всех пользователей. Следующий файл карты сайта, включенный в приложение, доступное в конце данной статьи, демонстрирует, как использовать атрибут roles для отображения всем пользователям узла карты сайта с ссылкой на внешний ресурс. (Следует помнить, что результатом опущения атрибута roles в приведенном случае будет то, что данный узел карты сайта не будет отображен никому из пользователей, если установлены ограничения по правам.)

<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <siteMapNode url="~/Default.aspx" title="Home">
    <siteMapNode url="~/About.aspx" title="About" />
    <siteMapNode url="~/Admin/Default.aspx" title="Admins" />
    <siteMapNode url="~/Tester/Default.aspx" title="Tester" />
    <siteMapNode url="~/AuthUsers/Default.aspx" title="Auth Users Only" />
    <!-- For links to outside resources, need to explicitly define what roles should be shown this section -->
    <siteMapNode url="http://www.scottonwriting.net/sowBlog/" title="My Blog" roles="*" />
  </siteMapNode>
</siteMap>

Атрибут roles также может быть использован для добавления некоторого улучшения функциональности характеристик ограничений по правам (security trimming). При использовании данных ограничений провайдер навигации по сайту автоматически проверяет правила авторизации для всех узлов, указанных в карте сайта. Вы можете обойти данную проверку для тех узлов, которые вы хотите отображать всем пользователям (например, Home и About в указанном выше примере), путем добавления roles="*". Добавляя данный атрибут, вы сократите стандартную проверку авторизации, тем самым улучшая функциональность ограничений по правам.


Модель провайдеров

Возможности навигации по сайту реализованы при помощи модели провайдеров (provider model), которая предоставляет стандартный интерфейс программ (API - класс SiteMap), но позволяет разработчикам добавить свою реализацию интерфейса во время выполнения. ASP.NET 2.0 предоставляется с единственной реализацией - XmlSiteMapProvider, с помощью которой разработчик может создать карту сайта используя XML-файл (Web.sitemap). Тем не менее, структура нашего сайта может быть сформирована информацией о существующей базе данных, либо каталогами и файлами, из которых составлен наш веб-сайт. Вместо того чтобы дублировать базу данных либо файловую систему в файле Web.sitemap, мы можем создать специализированный провайдер (custom provider), который использует базу данных или информацию о файловой системе в качестве карты сайта.

Благодаря провайдеру мы можем предоставить собственную реализацию под-системы навигации сайта, но при этом доступ к ней может быть осуществлен посредством класса SiteMap. В сущности, имея в наличии специализированный провайдер (custom provider), класс SiteMap и элементы управления навигацией будут работать точно также, как и в случае с XmlSiteMapProvider. Единственным отличием будет только то, что информация карты сайта будет заимствована от нашей созданной логической структуры, будь это база данных, веб-сервис, файловая система либо какой-нибудь другой источник данных, который требуется нашему приложению. В данной статье мы рассмотрим способ создания специализированной модели навигации по сайту и построим с нуля файловый провайдер карты сайта (File System-Based Site Map Provider). Читайте далее, чтобы узнать больше об этом!

Назначение файлового провайдера карты сайта (File System-Based Site Map Provider)

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

  1. Получать информацию карты навигации (она может быть базой данных, основанной на системе файлов и т.д.)
  2. Проходить по информации карты навигации, создавая узлы SiteMapNode для каждого логического раздела
  3. Добавлять узлы к карте сайта, тем самым формировать иерархию узлов, но при этом помнить о том, что может быть только один корневой узел.
В дополнение вам, скорее всего, придется написать некоторый код инициализации для специализированной модели карты сайта. При первом посещении карты сайта, после того как специализированный провайдер был зарегистрирован (custom provider) в файле Web.config, вызывается метод Initialize() данной модели и при этом также передаются названия атрибутов и их значения, указанные в файле Web.config провайдера. Метод Initialize() может считать данные названия и значения атрибутов и сохранять их там, где необходимо. Например, провайдеру карты сайта, который обратился к информации о структуре из базы данных, понадобится наличие строки соединения в разметке провайдера в файле Web.config. В методе Initialize() значение данной строки соединения может быть сохранено в поле (member variable ) класса. Впоследствии оно может быть использовано для произведения присоединения к базе данных при построении карты сайта.


Расширяем класс StaticSiteMapProvider

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

  • GetRootNodeCore() - возвращает корневой узел карты сайта.
  • BuildSiteMap() - формирует карту сайта и возвращает корневой узел.
В дополнение к данным двум методам существует набор других методов, которые могут быть перегружены при необходимости. Например, нам понадобится перегрузить метод Initialize() для любого специализированного провайдера, которому требуется считать специализированные настройки из файла Web.config.

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

Структура каталогов и файлов веб-сайта отражает его логическую структуру. Каталог Books содержал страницы, названные Novels.aspx, Romance.aspx, History.aspx и т.д. Аналогично были структурированы файлы в каталогах, названных Electronics, DVDs и Computers. Вместо того чтобы дублировать информацию о системе файлов в файле Web.sitemap (и помнить о том, что необходимо обновлять ее при добавлении новых страниц, переименовывать каталоги и файлы, либо удалять все страницы разом), мы можем создать специализированный провайдер карты сайта, который будет основываться на структуре файловой системы.

Я создал такой специализированный провайдер карты сайта и назвал его FileSystemSiteMapProvider. Данный  класс, вместе с веб-сайтом, использующим данный провайдер, доступен в конце статьи.

Целесообразность использования файлового провайдера карты сайта (File System-Based Site Map Provider)

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

Для сайтов, основанных на информации, вы наверняка захотите использовать специализированный провайдер карты сайта, который создает карту сайта, основываясь на информации из базы данных

Свойства провайдера FileSystemSiteMapProvider

По умолчанию, провайдер FileSystemSiteMapProvider подсчитывает все ASP.NET-страницы и каталоги веб-приложений, за исключением папки Bin и папок, начинающихся с префикса App_. Тем не менее могут существовать такие страницы или каталоги, которые вы не хотите отображать в карте сайта. Провайдер FileSystemSiteMapProvider предлагает два опциональных свойства, которые могут быть указаны для достижения данной цели:

  • ExcludeFileList - список тех файлов, которые не должны быть включены в карту сайта. Каждый файл должен полностью соответствовать условиям. Например, в диаграмме, указанной выше, представьте, что мы захотели бы исключить страницу Novels.aspx из карты сайта. Нам необходимо добавить ~/Books/Novels.aspx to ExcludeFileList.
  • ExcludeFoldeerList - список каталогов, которые мы также не хотим включать в карту сайта. Если папка исключена, все файлы и подкаталоги также исключаются. В то же время путь к каталогу также должен быть указан, например ~/DVDs/.

Оба свойства ExcludeFileList и ExcludeFoldeerList являются объектами StringDictionary; более того, оба являются Protected, тем самым означая, что вы можете с ними работать напрямую с вашей ASP.NET-страницы. Вместо этого вам придется использовать подходящие вспомогательные методы для добавления, удаления и пересчета данных свойств. (В качестве более подробного примера использования вспомогательных методов рассмотрите страницу SiteMap_Info.aspx в приложении к данной статье.) Тем не менее вы можете настроить данные свойства в их исходные значения при помощи файла Web.config, и мы это вскоре рассмотрим.

В дополнение к данным исключающим свойствам FileSystemSiteMapProvider содержит еще четыре других свойства:

  • RootUrl - полностью проверенный путь к странице, которая служит корневым узлом в карте сайта. Если данное значение не указано - значение по умолчанию равно ~/Default.aspx.
  • RootTitle - заголовок, который отобразится для корневого SiteMapNode. По умолчанию "Home".
  • UseDefaultPageAsFolderUrl - логическое свойство, значение которого указывает на то, необходимо ли использовать DefaultPage в качестве ссылки на папку или нет. Если данное свойство установлено в значении True (стандартное значение), то каждый созданный в карте SiteMapNode для каталога будет иметь свое собственное свойство Url установленное в ~/FOLDER/DefaultPage.aspx (если файл существует). Более того, файл ~/FOLDER/DefaultPage.aspx (если он существует) не добавляется как потомок каталога.

    Чтобы понять данные свойства, рассмотрите диаграмму логической структуры сайта, указанную выше. Представьте, что сайт имеет страницу ~/Books/Default.aspx. Если вы хотите, чтобы узел Books в карте сайта мог быть нажат и при этом мог переносить пользователя на ~/Books/Default.aspx, то оставьте данное свойство, равное True. Если же вы хотите, чтобы пользователь не мог нажать на узел Books, и вместо этого хотите добавить четвертого наследника узла Books, который посылает пользователя на ~/Books/Default.aspx, то установите данное свойство в False.
  • DefaultPageName - название файла для страницы, которая является документом по умолчанию. По умолчанию равно Default.aspx.


Создаем карту сайта

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

Вы также можете довольно легко сохранить дерево - просто создайте переменную _root SiteMapNode на уровне класса и назначьте данную переменную корню SiteMap. Такой подход позволяет построить карту сайта единожды, и затем сохраняет ее до того, как веб-приложение будет перезагружено либо будет отредактирован класс FileSystemSiteMapProvider. Данное сохранение может показаться слишком "агрессивным", но, тем не менее, в случае  если новые ASP.NET-страницы добавлены в файловую систему либо удаляются существующие, карта сайта не отобразит данные изменения.

Чтобы избавиться от данной проблемы, используйте объект CacheDependency, который может быть использован для слежения за набором файлов и/или каталогов. Чтобы быть более точным, я настраиваю его на корневой каталог (~/), и когда вызывается BuildSiteMap(), я проверяю файловую систему на какие-либо изменения, произошедшие с момента последнего вызова BuildSiteMap(). Если ничего не произошло - я просто возвращаю ссылку _root, так как нет никакой необходимости в перестройке карты сайта. Если же были произведены какие-либо изменения, то я заново собираю карту.

Метод BuildSiteMap() и рекурсивная BuildSiteMapFromFileSystem(), приведенные ниже, являются двигателями создания карты сайта. Я оставил парочку вспомогательных методов (к примеру, CreateFileNode() и CreateFolderNode()). Данные методы вы можете исследовать, скачав полный код в конце данной статьи.

Public Overrides Function BuildSiteMap()  As System.Web.SiteMapNode
    'Необходимо заблокировать для обеспечения безопасности процесса, поскольку может случиться и так, что несколько
    'страниц в приложении могут вызвать данный метод одновременно
    SyncLock Me
        'Проверяем, был ли корень определен
        If _root IsNot Nothing Then
            'У нас есть корень - но была ли файловая система изменена?
            If Not _fsMonitor.HasChanged Then
                'Никаких изменений не произошло - возвращаем сохраненный путь
                Return _root
            End If

            'Файловая система была изменена с момента последнего доступа
            'BuildSiteMap - нам необходимо пересобрать карту сайта
        End If

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

        'Создаем корневой узел
        _root = CreateFolderNode(HttpContext.Current.Server.MapPath(RootUrl), RootUrl)
        _root.Title = RootTitle

        'Устанавливаем зависимость от  сохраненной версии
        _fsMonitor = New CacheDependency(HttpContext.Current.Server.MapPath("~/"))

        AddNode(_root)  'Добавляем путь к карте сайта

        'Рекурсивно проходим по файловой системе, добавляя узлы
        BuildSiteMapFromFileSystem(_root, "~/")

        Return _root
    End SyncLock
End Function

Protected Sub BuildSiteMapFromFileSystem(ByVal parentNode As SiteMapNode, ByVal folderPath As String)
    'Определяем путь к каталогу текущего currentNode
    Dim folder As String = HttpContext.Current.Server.MapPath(folderPath)

    'Получаем информацию о каталоге
    Dim dirInfo As New DirectoryInfo(folder)

    'Добавляем файлы в дерево , которое имеет текущий currentNode в качестве родителя
    For Each fi As FileInfo In dirInfo.GetFiles("*.aspx")
        Dim fileNode As SiteMapNode = CreateFileNode(fi.FullName, parentNode, folderPath)
        If fileNode IsNot Nothing Then AddNode(fileNode, parentNode)
    Next

    'Добавляем узлы для каждой подпапки
    For Each di As DirectoryInfo In dirInfo.GetDirectories()
        Dim folderNode As SiteMapNode = CreateFolderNode(di.FullName, String.Concat(folderPath, di.Name, "/") & DefaultPageName)

        'Добавляем узел
        If folderNode IsNot Nothing Then
            AddNode(folderNode, parentNode)

            BuildSiteMapFromFileSystem(folderNode, String.Concat(folderPath, di.Name, "/"))
        End If
    Next
End Sub


Расширяем возможности провайдера FileSystemSiteMapProvider

Реализация FileSystemSiteMapProvider, продемонстрированная тут, использует очень простой алгоритм определения заголовка для SiteMapNodes файлов и папок - она просто использует название файла или папки, заменяя underscores (_) пробелами. Тем не менее, вы наверняка захотите установить в качестве основы названия файла значение элемента <title> (если таковое существует), или основываться на каком-то критерии либо тэге <meta>. Вы с легкостью можете добавить данную функциональность путем расширения класса FileSystemSiteMapProvider и перегрузки методов GetFileTitle() и GetFolderTitle(). Данные два метода возвращают заголовок, используемый SiteMapNode для указанного пути к файлу либо каталогу.


Внедрение провайдера FileSystemSiteMapProvider в веб-сайт

Последним шагом будет внедрение провайдера в ваш веб-сайт и использование его вместо стандартного XmlSiteMapProvider. Для этого добавьте следующую разметку в файл Web.config вашего приложения:

<configuration>
  <system.web>
    <!-- SiteMap Provider Configuration -->
    <siteMap enabled="true" defaultProvider="FileSystemSiteMapProvider">
      <providers>
        <add name="FileSystemSiteMapProvider"
            type="FileSystemSiteMapProvider"
            
            excludeFiles="comma-delimited list of files"
            excludeFolders="comma-delimited list of folders"
            rootUrl="rootUrl"
            rootTitle="rootTitle"
            useDefaultPageAsFolderUrl="true|false"
            defaultPageName="defaultPageName.aspx"
        />
      </providers>
    </siteMap>
   
    ...
  </system.web>
</configuration>

Все атрибуты в элементе <add> являются опциональными (за исключением name и type).


Ограничения прав доступа к узлам карты сайта (security trimming)

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

Модель провайдера карты сайта и ограничение прав (security trimming) применяются для настройки узлов карты сайта, используемых элементами управления навигации. Тем не менее, нам может понадобиться настроить обработанный результат элемента навигации, основываясь на информации карты сайта. Допустим, в нашем элементе управления Menu мы хотим отображать иконку, расположенную рядом с каждым пунктом меню, в зависимости от классификации, указанной для узла карты соответствующего пункта меню. В качестве альтернативы разметка, обработанная встроенными элементами управления ASP.NET, может оказаться непригодной. Вместо того, чтобы отображать TreeView либо Menu, нам наверняка захочется отобразить информацию навигации сайта в виде списка с маркерами. Данную функциональность можно получить при помощи класса SiteMap.

Добавление специализированных атрибутов к узлам карты сайта

В предыдущих главах данной серии статей мы обсуждали способ формирования логической навигационной структуры сайта в качестве карты сайта, которая является иерархическим набором узлов. Каждый узел карты сайта представлен в коде как экземпляр класса SiteMapNode, который обладает такими свойствами, как ChildNodes, ParentNode, Title, Url, и др. Следующая диаграмма отражает карту сайта; каждый блок в диаграмме является узлом карты.


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

// C#
siteMapNodeInstance["key"] = value;
object value = siteMapNodeInstance["key"];

' VB
siteMapNodeInstance("key") = value
Dim value as Object = siteMapNodeInstance("key")

Данные специализированные значения могут быть доступны при работе с узлами карты (например, в случае их отображения в элементе Menu), и выводы могут быть сделаны, основываясь на данных значениях.

Конечно, до того как мы начнем работу с данными значениями, нам нужно назначить их экземплярам SiteMapNode, из которых составлена карта сайта. Как вы с этим будете справляться - зависит от того, какой провайдер карты сайта вы используете. Если вы используете стандартный провайдер карты сайта (XmlSiteMapProvider - тот, который хранит карту сайта в файле с XML-кодом), специализированные значения могут быть добавлены как дополнительные атрибуты к элементам <siteMapNode>. К примеру, представьте, что вам нужно добавить изображение в каждый узел элемента TreeView и каждое изображение определить в карте сайта. Мы можем добавить атрибут imageUrl в различные элементы <siteMapNode> следующим образом:

<?xml version="1.0" encoding="utf-8"  ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
 <siteMapNode url="~/Default.aspx" title="Home">
   <siteMapNode url="~/Books/Default.aspx" title="Books"
                imageUrl="books.jpg">
    <siteMapNode url="~/Books/Novels.aspx" title="Novels"
                 imageUrl="books.jpg" />
    <siteMapNode url="~/Books/History.aspx" title="History"
                 imageUrl="books.jpg" />
    <siteMapNode url="~/Books/Romance.aspx" title="Romance"
                 imageUrl="Heart.gif" />
   </siteMapNode>
  
   <siteMapNode url="~/Electronics/Default.aspx" title="Electronics"
                imageUrl="electronics.jpg" />
   <siteMapNode url="~/DVDs/Default.aspx" title="DVDs"
                imageUrl="dvd.png" />
   <siteMapNode url="~/Computers/Default.aspx" title="Computers"
                imageUrl="computer.png" />
 </siteMapNode>
</siteMap>

Указав свойства imageUrl нам теперь нужно настроить элемент навигации, используемый для отображения информации, таким образом, чтобы он отображал указатель изображения (URL). Давайте рассмотрим пример использования элемента TreeView. Начнем с добавления SiteMapDataSource на страницу, затем добавим TreeView, свяжем TreeView с SiteMapDataSource. Далее, создадим обработчик для события TreeView TreeNodeDataBound. Данное событие запускается по разу для каждого узла TreeView после того, как пункт будет привязан к узлу TreeView.

Обработчик события передает экземпляр TreeNodeEventArgs в своем втором параметре, который обладает свойством Node, а тот в свою очередь возвращает привязанный узел TreeView. Узел TreeView обладает свойством DataItem, которое возвращает тот объект, который был привязан к TreeView. Во время использования TreeView для отображения информации карты сайта DataItem является конкретным экземпляром SiteMapNode на сайте и привязан к определенному узлу TreeView.

Следующий код проверяет, обладает ли текущий экземпляр SiteMapNode установленным специализированным значением imageUrl. Если это так, то оно устанавливает свойства узлов TreeView ImageUrl в ~/Images/value, где value является значением атрибута SiteMapNodeimageUrl.

'VB
Protected Sub TreeViewID_TreeNodeDataBound(ByVal sender As Object,
           ByVal e As TreeNodeEventArgs) Handles TreeViewID.TreeNodeDataBound

  'Ссылаемся на нижеследеющий объект SiteMapNode ...
  Dim nodeFromSiteMap As SiteMapNode = CType(e.Node.DataItem, SiteMapNode)
   
  'Если у нас есть значение imageUrl, назначим его свойству узда TreeView ImageUrl
  If nodeFromSiteMap("imageUrl") IsNot Nothing Then
    e.Node.ImageUrl = System.IO.Path.Combine("~/Images/", nodeFromSiteMap("imageUrl"))
  End If
End Sub

// C#
protected void TreeViewID_TreeNodeDataBound(object sender,
                                                   TreeNodeEventArgs e)
{
  // Ссылаемся на нижеследующий объект SiteMapNode ...
  SiteMapNode nodeFromSiteMap = (SiteMapNode) e.Node.DataItem;
  
  // Если у нас есть значение imageUrl, назначим его свойству узда TreeView ImageUrl
  if (nodeFromSiteMap("imageUrl") != null)
    e.Node.ImageUrl = System.IO.Path.Combine("~/Images/", nodeFromSiteMap["imageUrl"]);
}
  


Создаем специализированный пользовательский интерфейс навигации

ASP.NET 2.0 имеет в наличии три элемента управления которые обычно используются для отображения информации из карты сайта:

  • SiteMapPath - отображает пользователю его положение в иерархии карты сайта (Этот элемент управления обсуждался в второй части данной сериии статей.)
  • TreeView - отображает все узлы карты сайта в свертывающимся дереве (treeview)
  • Menu - отображает все узлы в карте сайта, в меню выровненном по горизонтали либо вертикали
Чтобы отобразить информацию карты сайта, оба элемента TreeView и Menu должны быть привязаны к SiteMapDataSource. SiteMapDataSource является элементом управления, возвращающим информацию о карте сайта в TreeView либо Menu в качестве объекта SiteMapNodeCollection, который содержит иерархию узлов. Элементы управления TreeView и Menu рекурсивно проходят по данному набору и выстраивают свои собственные пункты меню и узлы дерева соответственно.

Мы можем работать с SiteMapNodeCollection, возвращенным SiteMapDataSource, либо декларативно, либо программным путем:

<asp:Repeater runat="server"  ID="siteMapAsBulletedList"  DataSourceID="SiteMapDataSource1">
    <HeaderTemplate>
        <ul>
            <li><asp:HyperLink runat="server" ID="lnkHome" NavigateUrl='<%# SiteMap.RootNode.Url %>' Text='<%# SiteMap.RootNode.Title %>'></asp:HyperLink></li>
    </HeaderTemplate>

    <ItemTemplate>
        <li>
            <asp:HyperLink runat="server" NavigateUrl='<%# Eval("Url") %>' Text='<%# Eval("Title") %>'></asp:HyperLink>
        </li>
    </ItemTemplate>

    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>

<asp:SiteMapDataSource ShowStartingNode="false" ID="SiteMapDataSource1" runat="server" />

Здесь мы связали Repeater siteMapAsBulletedList с SiteMapDataSource1 элемента управления SiteMapDataSource. Учтите, что SiteMapDataSource возвращает иерархический объект SiteMapNodeCollection. Repeater, не будучи иерархическим элементом управления информацией, всего лишь перечисляет первый уровень результатов узлов карты сайта. То есть он не углубляется в дочерние узлы каждого SiteMapNode. В данном примере ShowStartingNode установлен в значение False, что заставляет SiteMapNodeCollection начать обработку со второго уровня (Books, DVD, Electroincs, и Computers). То есть для каждого SiteMapNode второго уровня инициируется ItemTemplate. (Корневой узел карты сайта (Home) отображается с небольшой разметкой в HeaderTemplate.)

При помощи декларативной разметки, указанной выше, мы получим следующий результат:


Это полезно использовать для отображения корневого узла карты сайта, а также первый уровень. Но что если мы хотим отобразить дополнительные уровни? Мы можем просто добавить другой Repeater в ItemTemplate, тем самым устанавливая DataSource того второго Repeater в свойство ChildNodes текущего используемого узла, примерно так:

<asp:Repeater runat="server"  ID="siteMapAsBulletedList"  DataSourceID="SiteMapDataSource1">
    <HeaderTemplate>...</HeaderTemplate>
    
    <ItemTemplate>
        <li>
            <asp:HyperLink runat="server" NavigateUrl='<%# Eval("Url") %>' Text='<%# Eval("Title") %>'></asp:HyperLink>

            <asp:Repeater runat="server" id="SecondLevel" DataSource='<%# CType(Container.DataItem, SiteMapNode).ChildNodes %>'>
                <HeaderTemplate><ul></HeaderTemplate>
                <ItemTemplate>
                    <li>
                        <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl='<%# Eval("Url") %>' Text='<%# Eval("Title") %>'></asp:HyperLink>
                    </li>
                </ItemTemplate>
                <FooterTemplate></ul></FooterTemplate>
            </asp:Repeater>
        </li>
    </ItemTemplate>
    
    <FooterTemplate>...</FooterTemplate>
</asp:Repeater>

<asp:SiteMapDataSource ShowStartingNode="false" ID="SiteMapDataSource1" runat="server" />

Для C#, свойство DataSource будет установлено как DataSource='<%# ((SiteMapNode) Container.DataItem).ChildNodes %>'

{mosimage}

Декларативный подход ограничен только отображением тех уровней карты, которые внедрены во множество Repeater. То есть, если узел Romance имел дополнительные подузлы, вышеупомянутая декларативная разметка не включала бы их в себя. Для того чтобы отобразить их, используя декларативную разметку, нам нужно добавить третий Repeater к ItemTemplate второго Repeater. Тем не менее, это не будет отображать все узлы сайта вплоть до 4 уровня.

Чтобы отобразить узлы любой "глубины" в списке нам необходимо программно задействовать SiteMapDataSource и рекурсивно пройтись по возвращенным объектом SiteMapNodeCollection. Следующий код (только в VB) демонстрирует способ реализации данной идеи. На странице присутствует элемент Label с ID bulletedList, и SiteMapDataSource с IDsiteMapData и его свойство ShowStartingNode установлено в значение False. (Данный код, а также все предыдущие примеры, доступны в конце данной статьи...)

Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) _
                                                      Handles Me.Load
  'Создаем список
  bulletedList.Text = _
      String.Format("<ul><li><a href=""{0}"">{1}</a></li>{2}</ul>", _
                    SiteMap.RootNode.Url, SiteMap.RootNode.Title, _
                    DisplaySiteMapLevelAsBulletedList())
End Sub

Private Function DisplaySiteMapLevelAsBulletedList() As String
  'Получаем SiteMapDataSourceView из siteMapData SiteMapDataSource
  Dim siteMapView As SiteMapDataSourceView = _
          CType(siteMapData.GetView(String.Empty), SiteMapDataSourceView)
 
  'Получаем SiteMapNodeCollection из SiteMapDataSourceView
  Dim nodes As SiteMapNodeCollection = _
          CType(siteMapView.Select(DataSourceSelectArguments.Empty), _
          SiteMapNodeCollection)

  'Рекурсивно проходим по SiteMapNodeCollection...
  Return GetSiteMapLevelAsBulletedList(nodes)
End Function

Private Function GetSiteMapLevelAsBulletedList(ByVal nodes As _
                                 SiteMapNodeCollection) As String
    Dim output As String = String.Empty
    For Each node As SiteMapNode In nodes
        output &= String.Format("<li><a href=""{0}"">{1}</a>", _
                                node.Url, node.Title)

        'Добавляем все дочерние уровни , если необходимо (рекурсивно)
        If node.HasChildNodes Then
            output &= String.Format("<ul>{0}</ul>", _
                        GetSiteMapLevelAsBulletedList(node.ChildNodes))
        End If

        output &= "</li>"
    Next

    Return output
End Function


Scott Mitchell

Исходный код примеров:

Часть 1, Часть 2 , Часть 3 , Часть 4 , Часть 5.