Создание пользовательского поставщика карт сайтов, управляемых базами данных (C#)

Скотт Митчелл

Скачать в формате PDF

Поставщик карты сайта по умолчанию в ASP.NET 2.0 извлекает данные из статического XML-файла. Хотя поставщик на основе XML подходит для многих небольших и средних веб-сайтов, для больших веб-приложений требуется более динамическая карта сайта. В этом руководстве мы создадим настраиваемый поставщик карты сайта, который извлекает данные из уровня бизнес-логики, который, в свою очередь, извлекает данные из базы данных.

Введение

функция карты сайта ASP.NET 2.0 позволяет разработчику страниц определять карту сайта веб-приложения в некоторой постоянной среде, например в XML-файле. После определения данные карты сайта можно получить программным способом через SiteMap класс в System.Web пространстве имен или с помощью различных элементов управления навигацией, таких как элементы управления SiteMapPath, Menu и TreeView. Система карты сайта использует модель поставщика, чтобы различные реализации сериализации карты сайта могли создаваться и подключаться к веб-приложению. Поставщик карты сайта по умолчанию, который поставляется с ASP.NET 2.0, сохраняет структуру карты сайта в XML-файле. Еще в руководстве по эталонным страницам и навигации по сайтам мы создали файл с именем Web.sitemap , который содержал эту структуру и обновлял XML с каждым новым разделом учебника.

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

Категории и продукты составляют структуру карты сайта.

Рис. 1: Категории и продукты составляют структуру карты сайта (щелкните, чтобы просмотреть изображение полного размера)

Хотя эта структура на основе категорий и продуктов может быть жестко закодирована в Web.sitemap файл, файл должен обновляться при каждом добавлении, удалении или переименовании категории или продукта. Следовательно, обслуживание карты сайта значительно упрощается, если его структура была получена из базы данных или, в идеале, из уровня бизнес-логики архитектуры приложения. Таким образом, при добавлении, переименовании или удалении продуктов и категорий карта сайта автоматически обновляется, чтобы отразить эти изменения.

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

Примечание.

Настраиваемая карта сайта, компонент которой создан в этом руководстве, тесно связана с архитектурой приложения и моделью данных. Джеффа Просайза Storing Site Maps in SQL Server и The SQL Site Map Provider You've Been Waiting For статьи изучают обобщенный подход к хранению данных карты сайта в SQL Server.

Шаг 1. Создание веб-страниц пользовательского поставщика карт сайта

Прежде чем приступить к созданию настраиваемого поставщика карты сайта, сначала добавьте страницы ASP.NET, необходимые для этого руководства. Сначала добавьте новую папку с именем SiteMapProvider. Затем добавьте в нее следующие ASP.NET страницы, чтобы связать каждую страницу с главной страницей Site.master :

  • Default.aspx
  • ProductsByCategory.aspx
  • ProductDetails.aspx

Также добавьте вложенную папку CustomProviders в папку App_Code.

Добавьте страницы ASP.NET для учебных материалов о провайдерах карт сайта

Рис. 2: Добавить страницы ASP.NET для учебных материалов, связанных с провайдером карт сайта

Так как в этом разделе есть только одно руководство, нам не нужно Default.aspx перечислять руководство этого раздела. Вместо этого Default.aspx будет отображать категории в элементе управления GridView. Мы будем решать эту проблему на шаге 2.

Затем обновите Web.sitemap, чтобы включить ссылку на страницу Default.aspx. В частности, добавьте следующую разметку после кэширования <siteMapNode>:

<siteMapNode 
    title="Customizing the Site Map" url="~/SiteMapProvider/Default.aspx" 
    description="Learn how to create a custom provider that retrieves the site map 
                 from the Northwind database." />

После обновления Web.sitemap просмотрите веб-сайт инструкций через браузер. Меню слева теперь включает пункт для единственного руководства по провайдеру карты сайта.

Карта сайта теперь включает запись о руководстве по поставщикам карт сайта

Рис. 3. Схема сайта теперь включает запись для руководства по поставщику карт сайта

В этом руководстве основное внимание уделяется созданию пользовательского поставщика карты сайта и настройке веб-приложения для использования этого поставщика. В частности, мы создадим провайдера, который возвращает карту сайта с корневым узлом и узлами для каждой категории и продукта, как показано на рис. 1. Как правило, каждый узел на карте сайта может указывать URL-адрес. URL-адрес корневого узла для нашей карты сайта — ~/SiteMapProvider/Default.aspx, он будет перечислять все категории в базе данных. Каждый узел категории на карте сайта будет иметь URL-адрес, указывающий на ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID, который будет перечислять все продукты в указанном идентификаторе категории. Наконец, каждый узел карты сайта каждого продукта будет указывать на ~/SiteMapProvider/ProductDetails.aspx?ProductID=productID, который отобразит сведения о конкретном продукте.

Чтобы начать, необходимо создать страницы Default.aspx, ProductsByCategory.aspx и ProductDetails.aspx. Эти страницы заполняются в шагах 2, 3 и 4 соответственно. Так как основной акцент этого руководства сделан на поставщиках карт сайта, и в предыдущих руководствах уже было рассмотрено создание многостраничных отчетов типа мастер/подробность, мы быстро рассмотрим шаги 2–4. Если вам требуется обновление при создании главных и подробных отчетов, охватывающих несколько страниц, обратитесь к руководству по фильтрации основных и подробных сведений по двум страницам .

Шаг 2. Отображение списка категорий

Откройте страницу в папке Default.aspx, и перетащите GridView из панели элементов в конструктор, установив значение ее свойства ID на Categories. Из смарт-тега GridView привязать его к новому объекту ObjectDataSource с именем CategoriesDataSource и настроить его таким образом, чтобы он извлекал данные с помощью CategoriesBLL метода класса GetCategories . Поскольку в этом GridView отображаются только категории и не предусмотрены возможности изменения данных, установите значение (Нет) для выпадающих списков на вкладках UPDATE, INSERT и DELETE.

Настройка ObjectDataSource для возврата категорий с помощью метода GetCategories

Рис. 4. Настройка ObjectDataSource для возврата категорий с помощью GetCategories метода (щелкните, чтобы просмотреть изображение полного размера)

Задайте раскрывающимся спискам на вкладках UPDATE, INSERT и DELETE значение Нет

Рис. 5: Задайте раскрывающиеся списки на вкладках UPDATE, INSERT и DELETE на (Нет) (Щелкните, чтобы просмотреть изображение полного размера)

После завершения работы мастера настройки источника данных Visual Studio добавит BoundField для CategoryID, CategoryName, Description, NumberOfProducts и BrochurePath. Измените GridView таким образом, чтобы он содержал только CategoryName и Description BoundFields, и обновите свойство HeaderText в CategoryName BoundField на "Категория".

Затем добавьте HyperLinkField и расположите его так, чтобы оно оказалось самым левым полем. Задайте для свойства DataNavigateUrlFields значение CategoryID, а для свойства DataNavigateUrlFormatString — значение ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}. Установите для свойства Text значение View Products.

Добавить HyperLinkField в GridView категорий

Рис. 6. Добавление HyperLinkField в Categories GridView

После создания ObjectDataSource и настройки полей GridView два элемента управления декларативной разметки будут выглядеть следующим образом:

<asp:GridView ID="Categories" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource" 
    EnableViewState="False">
    <Columns>
        <asp:HyperLinkField DataNavigateUrlFields="CategoryID" 
            DataNavigateUrlFormatString=
                "~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}"
            Text="View Products" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
        <asp:BoundField DataField="Description" HeaderText="Description" 
            SortExpression="Description" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" 
    TypeName="CategoriesBLL"></asp:ObjectDataSource>

На рисунке 7 показано Default.aspx, при просмотре через браузер. Щелкнув ссылку "Просмотр продуктов" категории, вы перейдете к ProductsByCategory.aspx?CategoryID=categoryID, который мы создадим на шаге 3.

Каждая категория указана вместе со ссылкой

Рис. 7. Каждая категория указана вместе со ссылкой "Просмотр продуктов" (щелкните, чтобы просмотреть изображение полного размера)

Шаг 3. Перечисление продуктов выбранной категории

Откройте страницу ProductsByCategory.aspx и добавьте GridView, назвав ее ProductsByCategory. Из смарт-тега привязать GridView к новому объекту ObjectDataSource с именем ProductsByCategoryDataSource. Настройте ObjectDataSource для использования ProductsBLL метода класса GetProductsByCategoryID(categoryID) и задайте раскрывающимся спискам значение (None) на вкладках UPDATE, INSERT и DELETE.

Использование метода GetProductsByCategoryID(categoryID) класса ProductsBLL

Рис. 8. Использование ProductsBLL метода класса GetProductsByCategoryID(categoryID) (щелкните, чтобы просмотреть изображение полного размера)

Последний шаг мастера настройки источника данных запрашивает источник параметров для categoryID. Так как эти сведения передаются через поле CategoryIDзапроса, выберите QueryString из раскрывающегося списка и введите CategoryID в текстовое поле QueryStringField, как показано на рис. 9. Чтобы завершить работу мастера, нажмите Готово.

Используйте поле строки запроса CategoryID для параметра categoryID

Рис. 9. Используйте CategoryID поле querystring для параметра categoryID (щелкните, чтобы просмотреть изображение полного размера)

После завершения работы мастера Visual Studio добавит соответствующие BoundFields и CheckBoxField в GridView для соответствующих полей данных продукта. Удалите все, кроме ProductName, UnitPriceи SupplierName BoundFields. Настройте эти три свойства BoundFields HeaderText для считывания Product, Price и Supplier соответственно. Форматируйте UnitPrice BoundField как валюту.

Затем добавьте HyperLinkField и переместите его в левую позицию. Присвойте свойству Text значение "Просмотреть детали", свойству DataNavigateUrlFields значение ProductID, и свойству DataNavigateUrlFormatString значение ~/SiteMapProvider/ProductDetails.aspx?ProductID={0}.

Добавьте поле гиперссылки

Рис. 10. Добавьте поле HyperLinkField «Просмотреть сведения», которое указывает на ProductDetails.aspx

После выполнения этих настроек декларативная разметка GridView и ObjectDataSource должны выглядеть следующим образом:

<asp:GridView ID="ProductsByCategory" runat="server" AutoGenerateColumns="False"
    DataKeyNames="ProductID" DataSourceID="ProductsByCategoryDataSource" 
    EnableViewState="False">
    <Columns>
        <asp:HyperLinkField DataNavigateUrlFields="ProductID" 
            DataNavigateUrlFormatString=
                "~/SiteMapProvider/ProductDetails.aspx?ProductID={0}"
            Text="View Details" />
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" 
            HeaderText="Price" HtmlEncode="False" 
            SortExpression="UnitPrice" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier" 
            ReadOnly="True" SortExpression="SupplierName" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsByCategoryDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL">
    <SelectParameters>
        <asp:QueryStringParameter Name="categoryID" 
            QueryStringField="CategoryID" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

Вернитесь к просмотру Default.aspx через браузер и щелкните ссылку "Просмотр продуктов" для напитков. Это переместит вас к ProductsByCategory.aspx?CategoryID=1, где вы увидите имена, цены и поставщиков продуктов в базе данных Northwind, которые принадлежат категории "Напитки" (см. рис. 11). Вы можете дополнительно улучшить эту страницу, чтобы включить ссылку для возврата пользователей на страницу описания категорий (Default.aspx) и элемент управления DetailsView или FormView, отображающий имя и описание выбранной категории.

Отображаются имена напитков, цены и поставщики

Рис. 11. Отображаются имена напитков, цены и поставщики (щелкните, чтобы просмотреть изображение полного размера)

Шаг 4. Отображение сведений о продукте

Последняя страница ProductDetails.aspxотображает выбранные сведения о продуктах. Откройте ProductDetails.aspx и перетащите DetailsView из Панели инструментов в конструктор. Установите для свойства ID объекта DetailsView значение ProductInfo и очистите значения его свойств Height и Width. Из смарт-тега привяжите DetailsView к новому ObjectDataSource с именем ProductDataSource, настроив его для извлечения данных из метода GetProductByProductID(productID) класса ProductsBLL. Как и в случае с предыдущими веб-страницами, созданными на этапах 2 и 3, установите раскрывающиеся списки в вкладках UPDATE, INSERT и DELETE на (Отсутствует).

Настройка ObjectDataSource для использования метода GetProductByProductID(productID)

Рис. 12. Настройка ObjectDataSource для использования GetProductByProductID(productID) метода (щелкните, чтобы просмотреть изображение полного размера)

Последний шаг мастера настройки источника данных запрашивает источник параметра productID . Так как эти данные передаются через поле ProductIDстроки запросов, задайте раскрывающийся список для QueryString и текстового поля QueryStringField значение ProductID. Наконец, нажмите кнопку "Готово", чтобы завершить работу мастера.

Настройка параметра productID для извлечения значения из поля запроса ProductID

Рис. 13. Настройка параметра productID для извлечения значения из ProductID поля запроса (щелкните, чтобы просмотреть изображение полного размера)

После завершения работы мастера настройки источника данных Visual Studio создаст соответствующие boundFields и CheckBoxField в DetailsView для полей данных продукта. Удалите ProductID, SupplierID и CategoryID BoundFields и настройте оставшиеся поля на ваше усмотрение. После нескольких визуальных настроек моя декларативная разметка в DetailsView и ObjectDataSource выглядела следующим образом:

<asp:DetailsView ID="ProductInfo" runat="server" AutoGenerateRows="False" 
    DataKeyNames="ProductID" DataSourceID="ProductDataSource" 
    EnableViewState="False">
    <Fields>
        <asp:BoundField DataField="ProductName" HeaderText="Product" 
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier" 
            ReadOnly="True" SortExpression="SupplierName" />
        <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit" 
            SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" 
            HeaderText="Price" HtmlEncode="False" 
            SortExpression="UnitPrice" />
        <asp:BoundField DataField="UnitsInStock" HeaderText="Units In Stock" 
            SortExpression="UnitsInStock" />
        <asp:BoundField DataField="UnitsOnOrder" HeaderText="Units On Order" 
            SortExpression="UnitsOnOrder" />
        <asp:BoundField DataField="ReorderLevel" HeaderText="Reorder Level" 
            SortExpression="ReorderLevel" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" 
            SortExpression="Discontinued" />
    </Fields>
</asp:DetailsView>
<asp:ObjectDataSource ID="ProductDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProductByProductID" TypeName="ProductsBLL">
    <SelectParameters>
        <asp:QueryStringParameter Name="productID" 
            QueryStringField="ProductID" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

Чтобы проверить эту страницу, вернитесь к Default.aspx и нажмите кнопку "Просмотр продуктов" для категории "Напитки". В списке напитков нажмите на ссылку «Просмотр сведений» для Чая. Это приведет вас на страницу ProductDetails.aspx?ProductID=1, на которой показаны подробности чая масала (см. рисунок 14).

Поставщик, категория, цена и другая информация о Чай Tea отображается

Рис. 14: Поставщик чая масала, категория, цена и другие сведения отображаются (щелкните, чтобы просмотреть изображение полного размера)

Шаг 5. Общие сведения о внутренней работе поставщика карты сайта

Карта сайта представлена в памяти веб-сервера в виде коллекции SiteMapNode экземпляров, которые образуют иерархию. Должно быть ровно один корень, все не корневые узлы должны иметь ровно один родительский узел, а все узлы могут иметь произвольное число дочерних узлов. Каждый объект представляет раздел в структуре веб-сайта. Эти SiteMapNode разделы обычно имеют соответствующую веб-страницу. Следовательно, класс SiteMapNode имеет такие свойства, как Title, Url и Description, которые предоставляют информацию о разделе, который представляет SiteMapNode. Существует также свойство Key, которое однозначно идентифицирует каждую SiteMapNode в иерархии, а также свойства, используемые для установления этой иерархии ChildNodes, ParentNode, NextSibling, PreviousSibling и т. д.

На рисунке 15 показана общая структура карты сайта на рис. 1, но с подробными сведениями о реализации.

Каждый SiteMapNode имеет свойства, такие как заголовок, URL-адрес, ключ и т. д.

Рис. 15. У каждого SiteMapNode есть свойства, например Title, UrlKeyи т. д. (Щелкните, чтобы просмотреть изображение полного размера)

Карта сайта доступна через SiteMap класс в пространстве имен System.Web. Это свойство RootNode класса возвращает корневой SiteMapNode экземпляр карты сайта; CurrentNode возвращает экземпляр SiteMapNode, свойство которого Url соответствует URL-адресу текущей запрошенной страницы. Этот класс используется внутренне в веб-элементах навигации управления ASP.NET 2.0.

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

Все поставщики карт сайта должны быть производными от SiteMapProvider класса, который включает в себя основные методы и свойства, необходимые для поставщиков карт сайта, но пропускает многие сведения о реализации. Второй класс расширяет StaticSiteMapProviderSiteMapProvider класс и содержит более надежную реализацию необходимых функций. Внутри StaticSiteMapProvider хранят экземпляры SiteMapNode карты сайта в Hashtable, и предоставляют такие методы, как AddNode(child, parent), RemoveNode(siteMapNode), и Clear() для добавления и удаления SiteMapNode во внутренний Hashtable. Класс XmlSiteMapProvider является производным от StaticSiteMapProvider.

При создании пользовательского поставщика схемы сайта, расширяющего StaticSiteMapProvider, существуют два абстрактных метода, которые необходимо переопределить: BuildSiteMap и GetRootNodeCore. BuildSiteMapКак подразумевает его имя, отвечает за загрузку структуры карты сайта из постоянного хранилища и создания ее в памяти. GetRootNodeCore возвращает корневой узел на карте сайта.

Прежде чем веб-приложение сможет использовать поставщик карты сайта, его необходимо зарегистрировать в конфигурации приложения. По умолчанию XmlSiteMapProvider класс регистрируется с помощью имени AspNetXmlSiteMapProvider. Чтобы зарегистрировать дополнительных поставщиков карт сайта, добавьте следующую разметку внутри Web.config:

<configuration>
    <system.web>
        ...
        <siteMap defaultProvider="defaultProviderName">
          <providers>
            <add name="name" type="type" />
          </providers>
        </siteMap>
    </system.web>
</configuration>

Значение имени присваивает поставщику удобочитаемое пользователем имя, а типзадает полное имя типа поставщика карты сайта. Мы рассмотрим конкретные значения для значений имен и типов в шаге 7 после создания пользовательского поставщика карты сайта.

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

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

Примечание.

Поставщик карты сайта может при необходимости переопределить Initialize метод. Initialize вызывается при первом создании экземпляра поставщика карты сайта, и ему передаются те пользовательские атрибуты, которые были назначены поставщику в Web.config, в элементе <add>, например: <add name="name" type="type" customAttribute="value" />. Полезно, если вы хотите разрешить разработчику страницы указывать различные параметры, связанные с поставщиком карты сайта, не изменяя код поставщика. Например, если бы мы считывали данные категории и продуктов непосредственно из базы данных вместо архитектурного слоя, нам, вероятно, потребуется позволить разработчику страницы указать строку подключения к базе данных через Web.config, вместо использования жестко запрограммированного значения в коде поставщика. Настраиваемый поставщик карты сайта, который мы создадим на шаге 6, не переопределяет этот Initialize метод. Пример использования метода Initialize, см. статью Джеффа Просиса "Storing Site Maps в SQL Server".

Шаг 6: Создание пользовательского обработчика карты сайта

Чтобы создать настраиваемый поставщик карты сайта, который создает карту сайта из категорий и продуктов в базе данных Northwind, необходимо создать класс, расширяющийся StaticSiteMapProvider. На шаге 1 я попросил добавить CustomProviders папку в App_Code папку — добавьте новый класс в эту папку с именем NorthwindSiteMapProvider. Добавьте в класс NorthwindSiteMapProvider следующий код.

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Web.Caching;
public class NorthwindSiteMapProvider : StaticSiteMapProvider
{
    private readonly object siteMapLock = new object();
    private SiteMapNode root = null;
    public const string CacheDependencyKey = 
        "NorthwindSiteMapProviderCacheDependency";
    public override SiteMapNode BuildSiteMap()
    {
        // Use a lock to make this method thread-safe
        lock (siteMapLock)
        {
            // First, see if we already have constructed the
            // rootNode. If so, return it...
            if (root != null)
                return root;
            // We need to build the site map!
            
            // Clear out the current site map structure
            base.Clear();
            // Get the categories and products information from the database
            ProductsBLL productsAPI = new ProductsBLL();
            Northwind.ProductsDataTable products = productsAPI.GetProducts();
            // Create the root SiteMapNode
            root = new SiteMapNode(
                this, "root", "~/SiteMapProvider/Default.aspx", "All Categories");
            AddNode(root);
            // Create SiteMapNodes for the categories and products
            foreach (Northwind.ProductsRow product in products)
            {
                // Add a new category SiteMapNode, if needed
                string categoryKey, categoryName;
                bool createUrlForCategoryNode = true;
                if (product.IsCategoryIDNull())
                {
                    categoryKey = "Category:None";
                    categoryName = "None";
                    createUrlForCategoryNode = false;
                }
                else
                {
                    categoryKey = string.Concat("Category:", product.CategoryID);
                    categoryName = product.CategoryName;
                }
                SiteMapNode categoryNode = FindSiteMapNodeFromKey(categoryKey);
                // Add the category SiteMapNode if it does not exist
                if (categoryNode == null)
                {
                    string productsByCategoryUrl = string.Empty;
                    if (createUrlForCategoryNode)
                        productsByCategoryUrl = 
                            "~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=" 
                            + product.CategoryID;
                    categoryNode = new SiteMapNode(
                        this, categoryKey, productsByCategoryUrl, categoryName);
                    AddNode(categoryNode, root);
                }
                // Add the product SiteMapNode
                string productUrl = 
                    "~/SiteMapProvider/ProductDetails.aspx?ProductID=" 
                    + product.ProductID;
                SiteMapNode productNode = new SiteMapNode(
                    this, string.Concat("Product:", product.ProductID), 
                    productUrl, product.ProductName);
                AddNode(productNode, categoryNode);
            }
            
            // Add a "dummy" item to the cache using a SqlCacheDependency
            // on the Products and Categories tables
            System.Web.Caching.SqlCacheDependency productsTableDependency = 
                new System.Web.Caching.SqlCacheDependency("NorthwindDB", "Products");
            System.Web.Caching.SqlCacheDependency categoriesTableDependency = 
                new System.Web.Caching.SqlCacheDependency("NorthwindDB", "Categories");
            // Create an AggregateCacheDependency
            System.Web.Caching.AggregateCacheDependency aggregateDependencies = 
                new System.Web.Caching.AggregateCacheDependency();
            aggregateDependencies.Add(productsTableDependency, categoriesTableDependency);
            // Add the item to the cache specifying a callback function
            HttpRuntime.Cache.Insert(
                CacheDependencyKey, DateTime.Now, aggregateDependencies, 
                Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, 
                CacheItemPriority.Normal, 
                new CacheItemRemovedCallback(OnSiteMapChanged));
            // Finally, return the root node
            return root;
        }
    }
    protected override SiteMapNode GetRootNodeCore()
    {
        return BuildSiteMap();
    }
    protected void OnSiteMapChanged(string key, object value, CacheItemRemovedReason reason)
    {
        lock (siteMapLock)
        {
            if (string.Compare(key, CacheDependencyKey) == 0)
            {
                // Refresh the site map
                root = null;
            }
        }
    }
    public DateTime? CachedDate
    {
        get
        {
            return HttpRuntime.Cache[CacheDependencyKey] as DateTime?;
        }
    }
}

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

Переменная SiteMapNode уровня root класса используется для кэширования структуры карты сайта. Когда карта сайта создается в первый раз или в первый раз после изменения базовых данных, будет создана структура карты сайта, и root станет null. Корневой узел карты сайта назначается root во время процесса построения, чтобы при следующем вызове этого метода root не был null. Следовательно, до тех пор, пока root не null, структура карты сайта будет возвращена вызывающему объекту без необходимости создавать её заново.

Если корневой каталог null, структура карты сайта создается из сведений о продукте и категории. Карта сайта создается путем создания экземпляров SiteMapNode, а затем иерархия формируется через вызовы метода StaticSiteMapProvider класса AddNode. AddNode занимается внутренним учетом, сохраняя разнообразные экземпляры SiteMapNode в Hashtable. Прежде чем приступить к созданию иерархии, мы начинаем с вызова метода Clear, который очищает элементы внутреннего Hashtable. ProductsBLL Затем метод класса GetProducts и результирующий ProductsDataTable результат хранятся в локальных переменных.

Построение карты сайта начинается с создания корневого узла и назначения его root. Перегрузка конструктораSiteMapNode здесь, и во всем этом BuildSiteMap передается следующая информация:

  • Ссылка на поставщика карты сайта (this).
  • SiteMapNodeSKey. Это необходимое значение должно быть уникальным для каждого SiteMapNode.
  • SiteMapNodeSUrl. Url является необязательным, но если указано, каждое SiteMapNodeUrl значение должно быть уникальным.
  • Параметр SiteMapNode s Title, обязательный для выполнения.

Вызов метода AddNode(root) добавляет SiteMapNoderoot в карту сайта в качестве корневого элемента. Далее перечисляется каждый ProductRow в ProductsDataTable. Если для текущей категории продукта уже существует SiteMapNode, оно используется в качестве ссылки. В противном случае создаётся новый элемент SiteMapNode для категории и добавляется в качестве дочернего элемента SiteMapNode``root методом AddNode(categoryNode, root) вызова. После того как соответствующий узел категории SiteMapNode найден или создан, SiteMapNode создается для текущего продукта и добавляется в качестве дочернего элемента категории SiteMapNode через AddNode(productNode, categoryNode). Обратите внимание, что значение свойства категории SiteMapNodeUrl равно ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID, в то время как свойство продукта SiteMapNodeUrl назначено на ~/SiteMapNode/ProductDetails.aspx?ProductID=productID.

Примечание.

Эти продукты, значение базы данных NULL для которых CategoryID, группируются под категорией SiteMapNode, свойство которой Title задано как None и свойство которой Url задано как пустая строка. Я решил установить для Url пустую строку, так как метод ProductBLL класса GetProductsByCategory(categoryID) в настоящее время не позволяет возвращать только те продукты, у которых есть значение NULLCategoryID. Кроме того, я хотел продемонстрировать, как элементы управления навигацией отображают значение SiteMapNode , которое не имеет значения для его Url свойства. Я призываю вас расширить это руководство так, чтобы свойство None SiteMapNodeUrl указывало на ProductsByCategory.aspx, но при этом отображало только продукты, имеющие значения NULLCategoryID.

После создания структуры сайта произвольный объект добавляется в кэш данных с помощью зависимости кэша SQL на таблицы Categories и Products через объект AggregateCacheDependency. Мы изучили использование зависимостей кэша SQL в предыдущем руководстве, Использование зависимостей кэша SQL. Однако компонент поставщика пользовательской карты сайта использует перегруженную версию метода кэша данных Insert, который мы еще не изучили. Эта перегрузка принимает в качестве конечного входного параметра делегат, вызываемый при удалении объекта из кэша. В частности, мы передаем новый CacheItemRemovedCallback делегат , указывающий на OnSiteMapChanged метод, определенный далее в NorthwindSiteMapProvider классе.

Примечание.

Представление карты сайта в памяти кэшируется с помощью переменной rootуровня класса. Так как существует только один экземпляр класса поставщика пользовательской карты сайта и так как этот экземпляр является общим для всех потоков в веб-приложении, эта переменная класса служит кэшем. Метод BuildSiteMap также использует кэш данных, но только в качестве средства для получения уведомлений при изменении базовых данных базы данных в Categories таблицах или Products таблицах. Обратите внимание, что значение, введенное в кэш данных, — это только текущая дата и время. Фактические данные карты сайта не помещаются в кэш данных.

Метод BuildSiteMap завершается путем возврата корневого узла карты сайта.

Остальные методы довольно просты. GetRootNodeCore отвечает за возврат корневого узла. Поскольку BuildSiteMap возвращает основной элемент, GetRootNodeCore просто возвращает возвращаемое значение BuildSiteMap. Метод OnSiteMapChanged возвращает root значение null , когда элемент кэша удаляется. При сбросе состояния к null, при следующем вызове BuildSiteMap структура карты сайта будет перестроена. Наконец, CachedDate свойство возвращает значение даты и времени, хранящееся в кэше данных, если такое значение существует. Это свойство можно использовать разработчиком страницы для определения времени последнего кэширования данных карты сайта.

Шаг 7. РегистрацияNorthwindSiteMapProvider

Чтобы веб-приложение использовало поставщика карты сайта, созданного NorthwindSiteMapProvider на шаге 6, необходимо зарегистрировать его в <siteMap> разделе Web.config. В частности, добавьте следующую разметку в элемент <system.web> внутри Web.config.

<siteMap defaultProvider="AspNetXmlSiteMapProvider">
  <providers>
    <add name="Northwind" type="NorthwindSiteMapProvider" />
  </providers>
</siteMap>

Эта разметка выполняет два действия: во-первых, она указывает, что встроенный поставщик карт сайта AspNetXmlSiteMapProvider используется по умолчанию; во-вторых, она регистрирует настраиваемый поставщик карты сайта, созданный на шаге 6, с удобным именем для пользователей Northwind.

Примечание.

Для поставщиков карт сайта, расположенных в папке приложения App_Code , значение type атрибута — это просто имя класса. Кроме того, поставщик пользовательской карты сайта мог быть создан в отдельном проекте библиотеки классов с скомпилированной сборкой, размещенной в каталоге веб-приложения /Bin . В этом случае значение атрибута type будет Namespace.ClassName, AssemblyName.

После обновления Web.config просмотрите любую страницу из учебников в интернет-браузере. Обратите внимание, что интерфейс навигации слева по-прежнему отображает разделы и руководства, определенные в Web.sitemap. Это связано с тем, что мы оставили AspNetXmlSiteMapProvider в качестве поставщика по умолчанию. Чтобы создать элемент пользовательского интерфейса навигации, использующий NorthwindSiteMapProvider, необходимо сначала явно указать, что следует использовать поставщика карты сайта Northwind. Мы посмотрим, как это сделать на шаге 8.

Шаг 8. Отображение информации о карте сайта с использованием пользовательского поставщика карт сайта

С созданным и зарегистрированным в Web.config пользовательским поставщиком карты сайта, мы готовы добавить элементы управления навигацией на страницы Default.aspx, ProductsByCategory.aspx и ProductDetails.aspx в папку SiteMapProvider. Начните с открытия Default.aspx страницы и перетащите SiteMapPath из панели инструментов в конструктор. Элемент управления SiteMapPath находится в разделе навигации панели элементов.

Добавьте SiteMapPath в Default.aspx

Рис. 16. Добавление SiteMapPath в Default.aspx (щелкните, чтобы просмотреть изображение полного размера)

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

Просмотрите эту страницу через браузер. SiteMapPath, добавленный на рис. 16, использует стандартного поставщика карты сайта, извлекая свои данные из Web.sitemap. Таким образом, в «хлебной крошке» отображается Главная > Настройка карты сайта, как и в «хлебной крошке» в правом верхнем углу.

Навигационная цепочка использует поставщика карты сайта по умолчанию

Рис. 17. Навигатор использует поставщика карты сайта по умолчанию (Щелкните, чтобы просмотреть изображение в полном размере)

Чтобы добавить SiteMapPath на рис. 16, используйте пользовательского поставщика карты сайта, созданного на шаге 6, и установите для его свойства SiteMapProvider значение Northwind, то имя, которое мы присвоили NorthwindSiteMapProvider в Web.config. К сожалению, дизайнер продолжает использовать поставщика карты сайта по умолчанию, но если вы посетите страницу через браузер после изменения этого свойства, вы увидите, что цепочка навигации теперь использует пользовательского поставщика карты сайта.

Снимок экрана, показывающий, как хлебные крошки отображают пользовательского провайдера карты сайта.

Рис. 18. Теперь хлебные крошки используют пользовательского поставщика карты сайта (нажмите, чтобы просмотреть изображение полного размера)

Элемент управления SiteMapPath отображает более функциональный пользовательский интерфейс на страницах ProductsByCategory.aspx и ProductDetails.aspx. Добавьте SiteMapPath на эти страницы, задав значение свойства SiteMapProvider в обоих случаях на Northwind. Щелкните Default.aspx ссылку "Просмотр товаров" для напитков, а затем на ссылку "Просмотр сведений" для чая. Как показано на рисунке 19, навигационная цепочка включает текущий раздел карты сайта (Chai Tea) и его вышестоящие разделы: Напитки и Все категории.

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

Рис. 19: Навигационная цепочка теперь использует пользовательского поставщика карты сайта NorthwindSiteMapProvider (щелкните, чтобы просмотреть изображение в полном размере)

Другие элементы пользовательского интерфейса навигации можно использовать в дополнение к SiteMapPath, таким как элементы управления Menu и TreeView. Default.aspx, ProductsByCategory.aspx, и ProductDetails.aspx страницы в загрузке этого руководства, например, все содержат элементы управления Меню (см. рис. 20). Дополнительную информацию об элементах управления навигацией и системе карт сайта в ASP.NET 2.0 можно найти в разделах "Сложные функции навигации в ASP.NET 2.0" и "Использование элементов управления навигацией" в быстром запуске ASP.NET 2.0.

Список элементов управления меню для каждой категории и продуктов

Рис. 20. Список элементов управления меню "Каждая из категорий и продуктов" (щелкните, чтобы просмотреть изображение полного размера)

Как упоминалось ранее в этом руководстве, структура карты сайта может быть доступна программным способом через класс SiteMap. Следующий код возвращает корень SiteMapNode провайдера по умолчанию:

SiteMapNode root = SiteMap.RootNode;

Поскольку AspNetXmlSiteMapProvider является поставщиком по умолчанию для нашего приложения, приведенный выше код вернет корневой узел, определенный в Web.sitemap. Чтобы ссылаться на поставщика карты сайта, отличного от используемого по умолчанию, используйте класс SiteMap и свойство Providers, как показано ниже.

SiteMapNode root = SiteMap.Providers["name"].RootNode;

Где имя — имя пользовательского поставщика карты сайта (Northwind, для нашего веб-приложения).

Для доступа к члену, специфичному для поставщика карты сайта, воспользуйтесь SiteMap.Providers["name"] для получения экземпляра поставщика и затем преобразования его к соответствующему типу. Например, чтобы отобразить NorthwindSiteMapProvider свойство s CachedDate на странице ASP.NET, используйте следующий код:

NorthwindSiteMapProvider customProvider = 
    SiteMap.Providers["Northwind"] as NorthwindSiteMapProvider;
if (customProvider != null)
{
    DateTime? lastCachedDate = customProvider.CachedDate;
    if (lastCachedDate != null)
        LabelID.Text = "Site map cached on: " + lastCachedDate.Value.ToString();
    else
        LabelID.Text = "The site map is being reconstructed!";
}

Примечание.

Обязательно протестируйте функцию зависимостей кэша SQL. После посещения страниц Default.aspx, ProductsByCategory.aspx, и ProductDetails.aspx откройте одно из руководств в разделе "Редактирование, вставка и удаление" и отредактируйте название категории или продукта. Затем вернитесь к одной из страниц в папке SiteMapProvider . Если достаточно времени прошло для механизма опроса, чтобы отметить изменение базовой базы данных, карта сайта должна быть обновлена, чтобы отобразить новое имя продукта или категории.

Итоги

Возможности карты сайта ASP.NET 2.0 включают в себя SiteMap класс, ряд встроенных средств навигации и поставщика карты сайта по умолчанию, который предполагает, что информация карты сайта сохраняется в XML-файле. Чтобы использовать сведения карты сайта из другого источника, например из базы данных, архитектуры приложения или удаленной веб-службы, необходимо создать настраиваемый поставщик карты сайта. Это предполагает создание класса, который наследуется напрямую или косвенно от класса SiteMapProvider.

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

Счастливое программирование!

Дополнительные материалы

Дополнительные сведения о разделах, описанных в этом руководстве, см. в следующих ресурсах:

Об авторе

Скотт Митчелл, автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с технологиями Microsoft Web с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams обучает ASP.NET 2.0 за 24 часа. С ним можно связаться по адресу mitchell@4GuysFromRolla.com.

Особое спасибо кому

Эта серия учебников была проверена многими полезными рецензентами. Ведущие рецензенты для этого руководства были Дэйв Гарднер, Зак Джонс, Тереса Мерфи и Бернадетт Ли. Хотите просмотреть мои предстоящие статьи MSDN? Если да, напишите мне на mitchell@4GuysFromRolla.com.