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

Примечание

Пользовательский поставщик карты сайта, созданный в этом руководстве, тесно связан с архитектурой приложения и моделью данных. Джефф Prosise в статьях Хранение карт сайтов в SQL Server и Поставщик карты сайта SQL, который вы ждали, рассматривают обобщенный подход к хранению данных карты сайта в SQL Server.

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

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

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

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

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

Рис. 2. Добавление страниц ASP.NET для Provider-Related учебников по карте сайта

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

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

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

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

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

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

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

Задайте для Drop-Down Списки на вкладках UPDATE, INSERT и DELETE значение (Нет)

Рис. 5. Задайте для Drop-Down Списки на вкладках UPDATE, INSERT и DELETE значение (Нет) (щелкните для просмотра полноразмерного изображения)

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

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

Добавление 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) и задайте для раскрывающихся списков значение (Нет) на вкладках UPDATE, INSERT и DELETE.

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

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

На последнем шаге мастера настройки источника данных запрашивается источник параметров для categoryID. Так как эти сведения передаются через поле CategoryIDquerystring , выберите 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 значение Просмотр сведений, свойству DataNavigateUrlFieldsProductIDзначение , а свойству DataNavigateUrlFormatString — значение ~/SiteMapProvider/ProductDetails.aspx?ProductID={0}.

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

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

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

На последнем шаге мастера настройки источника данных запрашивается источник параметра productID . Так как эти данные поступают через поле ProductIDquerystring , задайте в раскрывающемся списке значение 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, NextSibling, PreviousSiblingи т. д.

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

Каждый siteMapNode имеет такие свойства, как title, URL, key и т. д.

Рис. 15. Каждый из них SiteMapNode имеет такие свойства, как Title, Url, Keyи So On (щелкните для просмотра полноразмерного изображения)

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

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

Все поставщики карт сайта должны быть производными от SiteMapProvider класса , который включает основные методы и свойства, необходимые для поставщиков карт сайта, но пропускает многие сведения о реализации. Второй класс, , StaticSiteMapProviderрасширяет SiteMapProvider класс и содержит более надежную реализацию необходимых функций. На внутреннем языке класс хранит SiteMapNode экземпляры карты сайта в Hashtable и предоставляет такие методы, StaticSiteMapProvider как 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>

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

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

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

Примечание

При необходимости поставщик карты сайта может переопределить Initialize метод . Initialize вызывается при первом создании экземпляра поставщика карты сайта и передаче любых настраиваемых атрибутов, назначенных поставщику в Web.config элементе <add> , например: <add name="name" type="type" customAttribute="value" />. Это полезно, если вы хотите разрешить разработчику страниц указывать различные параметры, связанные с поставщиком карты сайта, без необходимости изменять код поставщика. Например, если мы считывали данные о категориях и продуктах непосредственно из базы данных, а не через архитектуру, скорее всего, мы хотели бы позволить разработчику страницы указать базу данных строка подключения черезWeb.config, а не использовать жестко закодированное значение в коде поставщика. Настраиваемый поставщик карты сайта, который мы создадим на шаге 6, не переопределяет этот Initialize метод. Пример использования Initialize метода см. в статье Jeff ProsiseStoring 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 , структура карты сайта будет возвращена вызывающему объекту без необходимости воссоздать ее.

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

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

  • Ссылка на поставщика карты сайта (this).
  • Объект SiteMapNode s Key. Это обязательное значение должно быть уникальным для каждого SiteMapNode.
  • Объект SiteMapNode s Url. 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 , а свойству продукта SiteMapNode присвоено ~/SiteMapNode/ProductDetails.aspx?ProductID=productIDUrl значение .

Примечание

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

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

Примечание

Представление карты сайта в памяти кэшируется с помощью переменной 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 будет пространством имен.ClassName, AssemblyName .

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

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

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

Добавление SiteMapPath в Default.aspx

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

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

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

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

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

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

Снимок экрана: отображение пользовательского поставщика карты сайта в строке навигации.

Рис. 18. Теперь в строке навигации используется пользовательский поставщик NorthwindSiteMapProvider карты сайта (щелкните для просмотра полноразмерного изображения)

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

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

Рис. 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;

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

Чтобы получить доступ к члену, относяскомуся к поставщику карты сайта, используйте SiteMap.Providers["name"] для получения экземпляра поставщика, а затем приведите его к соответствующему типу. Например, чтобы отобразить свойство s CachedDate на NorthwindSiteMapProvider странице 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 данных.

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

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

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

Об авторе

Скотт Митчелл( Scott Mitchell), автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с веб-технологиями Майкрософт с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams Teach Yourself ASP.NET 2.0 в 24 часах. Он может быть доступен в mitchell@4GuysFromRolla.com. или через его блог, который можно найти по адресу http://ScottOnWriting.NET.

Особая благодарность

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