Поделиться через


Создание пользовательского поставщика карт сайтов, управляемых базами данных (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. Давайте приступим!

Примечание.

Поставщик пользовательской карты сайта, созданный в этом руководстве, тесно связан с архитектурой приложения и моделью данных. Джефф Prosise s Storing Site Maps in SQL Server и Поставщик карты сайта SQL, которые вы ждали статьи, рассматривают обобщенный подход к хранению данных карты сайта в 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страницы и ProductDetails.aspx страницыProductsByCategory.aspx. Эти страницы выполняются в шагах 2, 3 и 4 соответственно. Так как в этом руководстве содержатся сведения о поставщиках карт сайта, и с тех пор, как прошлые учебники рассмотрели создание таких многостраничных эталонных и подробных отчетов, мы спешим по шагам 2–4. Если вам требуется обновление при создании главных и подробных отчетов, охватывающих несколько страниц, обратитесь к руководству по фильтрации основных и подробных сведений по двум страницам .

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

Default.aspx Откройте страницу в SiteMapProvider папке и перетащите 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и DescriptionNumberOfProducts.BrochurePath Измените GridView таким образом, чтобы он содержал CategoryName Description только свойства BoundFields и обновлял CategoryName свойство BoundField HeaderText на "Категория".

Затем добавьте 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. Чтобы завершить работу мастера, нажмите кнопку Готово .

Использование поля querystring CategoryID для параметра categoryID

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

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

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

Добавление hyperLinkField сведений о представлении, указывающее на ProductDetails.aspx

Рис. 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 из панели элементов в конструктор. Задайте для свойства ProductInfo DetailsView ID значение и удалите его Height и Width значения свойств. Из смарт-тега привязать DetailsView к новому объекту ObjectDataSource с именем ProductDataSource, настроив ObjectDataSource для извлечения данных из ProductsBLL метода класса GetProductByProductID(productID) . Как и в случае с предыдущими веб-страницами, созданными на шагах 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).

Поставщик чаи чая, категория, цена и другие сведения отображаются

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

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

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

На рисунке 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 класс и содержит более надежную реализацию необходимых функций. Внутри системы хранятся экземпляры карты сайта и Hashtable предоставляются такие методы, Clear() RemoveNode(siteMapNode), как AddNode(child, parent)добавление и удаление SiteMapNode s во внутреннийHashtable.StaticSiteMapProvider SiteMapNode Класс 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 метода см . в статье jeff Prosise s 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 позволяет одновременно вводить только один поток, тем самым сериализуя доступ к коду и предотвращая переход на два параллельных потока друг на друга.

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

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

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

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

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

Примечание.

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

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

Примечание.

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

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

Остальные методы довольно просты. GetRootNodeCore отвечает за возврат корневого узла. Так как BuildSiteMap возвращает корневой каталог, GetRootNodeCore просто возвращает BuildSiteMap возвращаемое значение. Метод OnSiteMapChanged возвращает root значение null , когда элемент кэша удаляется. При следующем nullBuildSiteMap вызове корневого набора структура карты сайта будет перестроена. Наконец, 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 будет пространством имен.ClassName, AssemblyName .

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

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

С помощью пользовательского поставщика карты сайта, созданного и зарегистрированногоWeb.config, мы готовы добавить элементы управления навигацией в Default.aspxProductsByCategory.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. Теперь хлебограмма использует поставщика NorthwindSiteMapProvider пользовательской карты сайта (щелкните, чтобы просмотреть изображение полного размера)

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

Снимок экрана: отображение текущего раздела карты сайта (Чай Чаи) и его предков (напитки и все категории).

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

Другие элементы пользовательского интерфейса навигации можно использовать в дополнение к SiteMapPath, таким как элементы управления Menu и TreeView. ProductsByCategory.aspxProductDetails.aspx Страницы Default.aspxи страницы в скачиваемом руководстве, например все элементы управления меню (см. рис. 20). Дополнительные сведения об элементах управления навигации и системе карт сайта в ASP.NET 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.aspxProductsByCategory.aspxстраниц и ProductDetails.aspx страниц перейдите к одному из учебников в разделе "Редактирование, вставка и удаление" и измените имя категории или продукта. Затем вернитесь к одной из страниц в папке SiteMapProvider . Если достаточно времени прошло для механизма опроса, чтобы отметить изменение базовой базы данных, карта сайта должна быть обновлена, чтобы отобразить новое имя продукта или категории.

Итоги

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

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

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

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

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

Об авторе

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

Особое спасибо

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