Dela via


Skapa en anpassad Database-Driven sitemapleverantör (C#)

av Scott Mitchell

Ladda ned PDF

Standardprovidern för webbplatsöversikt i ASP.NET 2.0 hämtar sina data från en statisk XML-fil. Den XML-baserade providern är lämplig för många små och medelstora webbplatser, men större webbprogram kräver en mer dynamisk webbplatskarta. I den här handledningen skapar vi en anpassad sajtöversiktsleverantör som hämtar sina data från affärslogiklagret, som i sin tur hämtar data från databasen.

Inledning

ASP.NET 2.0:s webbplatskartsfunktion gör det möjligt för en sidutvecklare att definiera en webbapplikations webbplatskarta i något beständigt medium, till exempel i en XML-fil. När de har definierats kan webbplatsöversiktsdata nås programmatiskt via SiteMap klassen i System.Web namnområdet eller via en mängd olika navigeringswebbkontroller, till exempel kontrollerna SiteMapPath, Menu och TreeView. Platskartsystemet använder providermodellen så att olika implementeringar av webbplatskartans serialisering kan skapas och anslutas till ett webbprogram. Standardprovidern för webbplatskartor som levereras med ASP.NET 2.0 bevarar webbplatskartans struktur i en XML-fil. I självstudien Huvudsidor och Webbplatsnavigering skapade vi en fil med namnet Web.sitemap som innehöll den här strukturen och har uppdaterat dess XML med varje nytt självstudieavsnitt.

Standardprovidern för XML-baserad webbplatskarta fungerar bra om webbplatskartans struktur är ganska statisk, till exempel för de här självstudierna. I många scenarier behövs dock en mer dynamisk webbplatskarta. Överväg webbplatskartan som visas i bild 1, där varje kategori och produkt visas som avsnitt i webbplatsens struktur. Med den här webbplatskartan kan det hända att en lista över alla kategorier visas på den webbsida som motsvarar rotnoden, medan en viss kategoris webbsida visar den kategorins produkter och en viss produkts webbsida visar informationen om produkten.

Kategorierna och produkterna utgör webbplatskartans struktur

Bild 1: Kategorierna och produkterna sminkar webbplatskartans struktur (Klicka om du vill visa en bild i full storlek)

Även om den här kategori- och produktbaserade strukturen kan hårdkodas i Web.sitemap filen, måste filen uppdateras varje gång en kategori eller produkt har lagts till, tagits bort eller bytt namn. Därför skulle underhållet av webbplatskartan förenklas avsevärt om dess struktur hämtades från databasen eller helst från affärslogiklagret i programmets arkitektur. På så sätt, när produkter och kategorier har lagts till, bytt namn eller tagits bort, uppdateras webbplatskartan automatiskt för att återspegla dessa ändringar.

Eftersom ASP.NET 2,0-s webbplatskartans serialisering har skapats ovanpå providermodellen kan vi skapa en egen anpassad webbplatsöversiktsprovider som hämtar sina data från ett alternativt datalager, till exempel databasen eller arkitekturen. I den här handledningen kommer vi att skapa en anpassad tjänst som hämtar sina data från BLL:n. Låt oss komma igång!

Anmärkning

Den anpassade webbplatsöversiktsprovidern som skapades i den här självstudien är nära kopplad till programmets arkitektur och datamodell. Jeff Prosises artiklar Storing Site Maps in SQL Server och The SQL Site Map Provider You’ve Been Waiting For undersöker ett generellt tillvägagångssätt för att lagra webbplatskartsdata i SQL Server.

Steg 1: Skapa webbsidor för anpassad webbplatskartaleverantör

Innan vi börjar skapa en anpassad webbplatsöversiktsprovider ska vi först lägga till de ASP.NET sidor som vi behöver för den här självstudien. Börja med att lägga till en ny mapp med namnet SiteMapProvider. Lägg sedan till följande ASP.NET sidor i mappen och se till att associera varje sida med Site.master huvudsidan:

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

Lägg också till en CustomProviders undermapp i App_Code mappen.

Lägg till ASP.NET sidor för webbplatsöversikten Provider-Related självstudier

Bild 2: Lägg till ASP.NET sidor för webbplatsöversikten Provider-Related självstudier

Eftersom det bara finns en självstudiekurs för det här avsnittet, behöver vi inte Default.aspx lista avsnittets självstudiekurser. Default.aspx I stället visas kategorierna i en GridView-kontroll. Vi tar itu med detta i steg 2.

Uppdatera Web.sitemap sedan för att inkludera en referens till Default.aspx sidan. Mer specifikt lägger du till följande markering efter cachelagringen <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." />

Efter att du har uppdaterat Web.sitemap, ta en stund att besöka självstudiewebbplatsen via en webbläsare. Menyn till vänster innehåller nu ett objekt för handledningen för den enda leverantören av webbplatskartor.

Webbplatsöversikten innehåller nu en post för handledningen om webbplatsöversiktsleverantörer

Bild 3: Webbplatsöversikten innehåller nu en post för självstudiekursen för webbplatsöversiktsprovider

Den här självstudiekursen fokuserar främst på att illustrera hur du skapar en anpassad webbplatsöversiktsprovider och konfigurerar ett webbprogram för att använda den providern. I synnerhet skapar vi en provider som returnerar en webbplatskarta som innehåller en rotnod tillsammans med en nod för varje kategori och produkt, enligt bild 1. I allmänhet kan varje nod i webbplatsöversikten ange en URL. För vår webbplatskarta blir ~/SiteMapProvider/Default.aspxrotnodens URL , som listar alla kategorier i databasen. Varje kategorinod i webbplatsöversikten har en URL som pekar på ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID, som visar alla produkter i det angivna categoryID:et. Slutligen pekar varje nod på produktwebbplatskartan på ~/SiteMapProvider/ProductDetails.aspx?ProductID=productID, som visar den specifika produktinformationen.

För att börja måste vi skapa Default.aspxsidorna , ProductsByCategory.aspxoch ProductDetails.aspx . Dessa sidor har slutförts i steg 2, 3 respektive 4. Eftersom syftet med den här självstudien är att fokusera på leverantörer av webbplatskartor, och eftersom tidigare självstudier har täckt hur man skapar den här typen av master/detalj-rapporter med flera sidor, kommer vi att snabbt gå igenom steg 2 till 4. Om du behöver en uppdatering för att skapa huvud-/detaljrapporter som sträcker sig över flera sidor kan du gå tillbaka till självstudien Master/Detail Filtering Across Two Pages (Huvud-/detaljfiltrering över två sidor ).

Steg 2: Visa en lista med kategorier

Öppna sidan Default.aspx i SiteMapProvider mappen och dra en GridView från verktygslådan till designern och ställ in den IDCategories. Från GridViews smarta tagg binder du den till en ny ObjectDataSource med namnet CategoriesDataSource och konfigurerar den så att den hämtar sina data med hjälp av CategoriesBLL klassens GetCategories -metod. Eftersom den här GridView bara visar kategorierna och inte tillhandahåller funktioner för datamodifiering anger du listrutorna på flikarna UPDATE, INSERT och DELETE till (Ingen) .

Konfigurera ObjectDataSource för att returnera kategorier med metoden GetCategories

Bild 4: Konfigurera ObjectDataSource för att returnera kategorier med hjälp av GetCategories metoden (Klicka om du vill visa en bild i full storlek)

Ange Drop-Down-listorna i flikarna UPDATE, INSERT och DELETE till (Ingen)

Bild 5: Ange Drop-Down-listorna i flikarna UPDATE, INSERT och DELETE till (Ingen) (Klicka om du vill visa en bild i full storlek)

När du har slutfört guiden Konfigurera datakälla lägger Visual Studio till ett BoundField för CategoryID, CategoryName, Description, NumberOfProductsoch BrochurePath. Redigera GridView så att den bara innehåller CategoryName och Description BoundFields, och uppdatera CategoryName BoundField s HeaderText egenskap till Kategori.

Lägg sedan till ett HyperLinkField och placera det så att det är det vänstra fältet. Ange egenskapen DataNavigateUrlFields till CategoryID och egenskapen DataNavigateUrlFormatString till ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}. Ange egenskapen Text till Visa produkter .

Lägga till ett HyperLinkField i kategorierna GridView

Bild 6: Lägg till ett HyperLinkField i Categories GridView

När du har skapat ObjectDataSource och anpassat GridView-fälten ser de två kontrollernas deklarativa markering ut så här:

<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>

Bild 7 visas Default.aspx när den visas via en webbläsare. Om du klickar på länken Visa produkter för en kategori går du till ProductsByCategory.aspx?CategoryID=categoryID, som vi kommer att skapa i steg 3.

Varje kategori visas tillsammans med länken Visa produkter

Bild 7: Varje kategori visas tillsammans med länken Visa produkter (Klicka om du vill visa en bild i full storlek)

Steg 3: Visa en lista över de valda kategoriprodukterna

Öppna sidan ProductsByCategory.aspx och lägg till en GridView och namnge den ProductsByCategory. Från den smarta taggen binder du GridView till en ny ObjectDataSource med namnet ProductsByCategoryDataSource. Konfigurera ObjectDataSource att använda ProductsBLL klassens GetProductsByCategoryID(categoryID) -metod och ange listrutorna till (Ingen) på flikarna UPDATE, INSERT och DELETE.

Använda metoden GetProductsByCategoryID(categoryID) för ProductsBLL-klassen

Bild 8: Använd ProductsBLL klassens GetProductsByCategoryID(categoryID) metod (Klicka om du vill visa en bild i full storlek)

Det sista steget i guiden Konfigurera datakälla frågar efter en parameterkälla för categoryID. Eftersom den här informationen skickas via frågesträngsfältet CategoryIDväljer du QueryString i listrutan och anger CategoryID i textrutan QueryStringField enligt bild 9. Klicka på Slutför för att slutföra guiden.

Använd fältet CategoryID Querystring för parametern categoryID

Bild 9: Använd frågesträngsfältet CategoryID för parametern categoryID (Klicka om du vill visa en bild i full storlek)

När du har slutfört guiden lägger Visual Studio till motsvarande BoundFields och ett CheckBoxField till GridView för produktdatafälten. Ta bort alla utom ProductName, UnitPriceoch SupplierName BoundFields. Anpassa dessa tre BoundFields-egenskaper HeaderText så att de läser Produkt, Pris respektive Leverantör. UnitPrice Formatera BoundField som en valuta.

Lägg sedan till ett HyperLinkField och flytta det till den vänstra positionen. Ange dess Text egenskap till Visa information, dess DataNavigateUrlFields egenskap till ProductIDoch dess DataNavigateUrlFormatString egenskap till ~/SiteMapProvider/ProductDetails.aspx?ProductID={0}.

Lägg till en HyperLinkField för visningsinformation som pekar på ProductDetails.aspx

Bild 10: Lägg till en Visa detaljer HyperLinkField som pekar på ProductDetails.aspx

När du har gjort de här anpassningarna bör GridView och ObjectDataSources deklarativa markering likna följande:

<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>

Gå tillbaka till visning Default.aspx via en webbläsare och klicka på länken Visa produkter för Drycker. Detta tar dig till ProductsByCategory.aspx?CategoryID=1och visar namn, priser och leverantörer av produkterna i Northwind-databasen som tillhör kategorin Drycker (se bild 11). Du kan förbättra den här sidan ytterligare så att den innehåller en länk för att returnera användare till kategorilistningssidan (Default.aspx) och en DetailsView- eller FormView-kontroll som visar den valda kategorins namn och beskrivning.

Dryckernas namn, priser och leverantörer visas

Bild 11: Dryckernas namn, priser och leverantörer visas (Klicka om du vill visa en bild i full storlek)

Steg 4: Visa information om en produkt

Den sista sidan, ProductDetails.aspx, visar den valda produktinformationen. Öppna ProductDetails.aspx och dra en DetailsView från Verktygslådan till Designern. Ange egenskapen ID för DetailsView till ProductInfo och rensa värdena för dess egenskaper Height och Width. Från den smarta taggen binder du DetailsView till en ny ObjectDataSource med namnet ProductDataSourceoch konfigurerar ObjectDataSource för att hämta data från ProductsBLL klassens GetProductByProductID(productID) -metod. Precis som med föregående webbsidor som skapades i steg 2 och 3 anger du listrutorna i flikarna UPPDATERA, INFOGA och TA BORT till (Ingen) .

Konfigurera ObjectDataSource att använda metoden GetProductByProductID(productID)

Bild 12: Konfigurera ObjectDataSource att använda GetProductByProductID(productID) metoden (Klicka om du vill visa en bild i full storlek)

Det sista steget i guiden Konfigurera datakälla frågar efter källan för parametern productID . Eftersom dessa data kommer via frågesträngsfältet ProductID, ställer du in listrutan på QueryString och textrutan QueryStringField på ProductID. Klicka slutligen på knappen Slutför för att slutföra guiden.

Konfigurera productID-parametern för att hämta dess värde från fältet ProductID Querystring

Bild 13: Konfigurera productID-parametern för att hämta dess värde från frågesträngsfältet ProductID (Klicka om du vill visa en bild i full storlek)

När du har slutfört guiden Konfigurera datakälla skapar Visual Studio motsvarande BoundFields och ett CheckBoxField i DetailsView för produktdatafälten. Ta bort ProductID, SupplierID och CategoryID BoundFields och konfigurera de kvarvarande fälten efter behov. Efter en handfull estetiska konfigurationer såg min DetailsView och ObjectDataSources deklarativa markering ut så här:

<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>

Om du vill testa den här sidan går du tillbaka till Default.aspx och klickar på Visa produkter för kategorin Drycker. Från listan över dryckesprodukter klickar du på länken Visa information för Chai Tea. Detta tar dig till ProductDetails.aspx?ProductID=1, som visar detaljerna om en Chai-te (se bild 14).

Chai Tea s Leverantör, Kategori, Pris och Annan information visas

Bild 14: Chai Teas Leverantör, Kategori, Pris och Annan information visas (Klicka om du vill visa en bild i full storlek)

Steg 5: Förstå det inre arbetet hos en webbplatsöversiktsprovider

Webbplatskartan representeras i webbserverns minne som en samling SiteMapNode instanser som utgör en hierarki. Det måste finnas exakt en rot, alla icke-rotnoder måste ha exakt en överordnad nod och alla noder kan ha ett godtyckligt antal underordnade. Varje SiteMapNode objekt representerar ett avsnitt i webbplatsens struktur. Dessa avsnitt har vanligtvis en motsvarande webbsida. Klassen har därförSiteMapNode egenskaper som Title, Urloch Description, som ger information för avsnittet som SiteMapNode representerar. Det finns också en egenskap som unikt identifierar var och en Key i hierarkin, samt egenskaper som används för att upprätta den här hierarkin SiteMapNode, ChildNodes, ParentNode, NextSiblingoch så PreviousSibling vidare.

Bild 15 visar den allmänna webbplatskartstrukturen från bild 1, men med implementeringsinformationen skissad i detalj.

Varje SiteMapNode har egenskaper som rubrik, URL, nyckel och så vidare

Bild 15: Var och en SiteMapNode har egenskaper som Title, Url, Keyoch Så vidare (Klicka om du vill visa en bild i full storlek)

Webbplatsöversikten SiteMap är tillgänglig via klassen i System.Web namnområdet. Den här klassens RootNode egenskap returnerar platskartans rotinstans SiteMapNode . CurrentNode Returnerar den SiteMapNode vars Url egenskap matchar URL:en för den begärda sidan. Den här klassen används internt av ASP.NET 2.0-navigeringswebbkontroller.

SiteMap När klassens egenskaper används måste den serialisera webbplatsöversiktsstrukturen från ett beständigt medium till minnet. Platskartans serialiseringslogik är dock inte hårdkodad i SiteMap klassen. Istället avgör klassen vid körning vilken SiteMap som ska användas för serialisering. Som standard XmlSiteMapProvider används klassen , som läser webbplatskartans struktur från en korrekt formaterad XML-fil. Men med lite arbete kan vi skapa en egen anpassad webbplatsöversiktsleverantör.

Alla webbplatskartproviders måste härledas från SiteMapProvider-klassen, vilket inkluderar de viktiga metoder och egenskaper som krävs för webbplatskartproviders, men utelämnar många av implementeringsdetaljerna. En andra klass, StaticSiteMapProvider, utökar SiteMapProvider klassen och innehåller en mer robust implementering av nödvändiga funktioner. Internt StaticSiteMapProvider lagrar SiteMapNode instanserna av webbplatskartan i en Hashtable och tillhandahåller metoder som AddNode(child, parent), RemoveNode(siteMapNode), och Clear() som lägger till och tar bort SiteMapNode s i den interna Hashtable. XmlSiteMapProvider härleds från StaticSiteMapProvider.

När du skapar en anpassad webbplatsöversiktsprovider som utökar StaticSiteMapProviderfinns det två abstrakta metoder som måste åsidosättas: BuildSiteMap och GetRootNodeCore. BuildSiteMap, som namnet antyder, ansvarar för att läsa in webbplatskartans struktur från beständig lagring och konstruera den i minnet. GetRootNodeCore returnerar rotnoden på platskartan.

Innan ett webbprogram kan använda en webbplatsöversiktsprovider måste det registreras i programmets konfiguration. Som standard registreras klassen XmlSiteMapProvider med namnet AspNetXmlSiteMapProvider. För att registrera ytterligare sitemap-leverantörer, lägg till följande markup i Web.config:

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

Namnvärdet tilldelar ett läsbart namn till providern medan typen anger webbplatsöversiktsproviderns fullständigt kvalificerade typnamn. Vi utforskar konkreta värden för namn och typ i Steg 7, efter att vi har skapat vår anpassade webbplatskarta leverantör.

Webbplatskartans providerklass instansieras första gången den nås från SiteMap-klassen och förblir i minnet under hela webbprogrammets livslängd. Eftersom det bara finns en instans av sitemap-leverantören som kan anropas från flera samtidiga webbplatsbesökare är det absolut nödvändigt att leverantörens methoder är trådsäkra.

Av prestanda- och skalbarhetsskäl är det viktigt att vi cachelagra den minnesinterna webbplatsöversiktsstrukturen och returnerar den cachelagrade strukturen i stället för att återskapa den BuildSiteMap varje gång metoden anropas. BuildSiteMap kan anropas flera gånger per sidbegäran per användare, beroende på vilka navigeringskontroller som används på sidan och djupet i webbplatskartans struktur. Om vi i vilket fall som helst inte cachelagr webbplatsöversiktsstrukturen BuildSiteMap varje gång den anropas måste vi hämta produkt- och kategoriinformationen igen från arkitekturen (vilket skulle resultera i en fråga till databasen). Som vi diskuterade i de tidigare självstudierna för cachelagring kan cachelagrade data bli inaktuella. För att bekämpa detta kan vi använda antingen tids- eller SQL-cacheberoendebaserade förfallodatum.

Anmärkning

En webbplatsöversiktsprovider kan eventuellt åsidosätta Initialize metoden. Initialize anropas när sitemap-providern först instansieras och får de anpassade attribut som tilldelats providern i Web.config-elementet <add>, såsom: <add name="name" type="type" customAttribute="value" />. Det är användbart om du vill tillåta att en sidutvecklare anger olika providerrelaterade inställningar för webbplatskartan utan att behöva ändra providerns kod. Om vi till exempel läste kategori- och produktdata direkt från databasen i stället för via arkitekturen, skulle vi förmodligen vilja låta sidutvecklaren ange databasanslutningssträngen i Web.config stället för att använda ett hårdkodat värde i providerkoden. Den anpassade webbplatsöversiktsprovidern som vi skapar i steg 6 åsidosätter inte den här Initialize metoden. Ett exempel på hur du använder metoden finns i Initialize artikeln Jeff Prosise s Storing Site Maps in SQL Server .

Steg 6: Skapa en anpassad sajtöversiktshanterare

Om du vill skapa en anpassad webbplatsöversiktsprovider som skapar webbplatskartan från kategorierna och produkterna i Northwind-databasen måste vi skapa en klass som utökar StaticSiteMapProvider. I steg 1 bad jag dig att lägga till en CustomProviders mapp i App_Code mappen – lägg till en ny klass i den här mappen med namnet NorthwindSiteMapProvider. Lägg till följande kod i NorthwindSiteMapProvider klassen:

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?;
        }
    }
}

Låt oss börja med att utforska den här klassens BuildSiteMap metod, som börjar med en lock -instruktion. Instruktionen lock tillåter endast en tråd i taget att ange, vilket serialiserar åtkomsten till koden och förhindrar att två samtidiga trådar trampar på varandras tår.

På klassnivå används variabeln SiteMapNode för att cachelagra sajtkartans struktur. När webbplatsöversikten byggs för första gången, eller för första gången efter att den underliggande datan har ändrats, kommer root att vara null och platskartans struktur kommer att konstrueras. Platskartans rotnod tilldelas root under byggprocessen så att nästa gång den här metoden anropas, kommer root inte att vara null. Så länge root inte är null, kommer webbplatskartans struktur att returneras till anroparen utan att behöva återskapas.

Om roten är nullskapas webbplatsöversiktsstrukturen från produkt- och kategoriinformationen. Webbplatskartan skapas genom att skapa SiteMapNode instanserna och sedan bilda hierarkin genom anrop till StaticSiteMapProvider klassens AddNode -metod. AddNode utför den interna bokföringen och lagrar de olika SiteMapNode instanserna i en Hashtable. Innan vi börjar skapa hierarkin börjar vi med att anropa Clear metoden som rensar ut elementen från den interna Hashtable. ProductsBLL Därefter lagras klassens GetProducts metod och resultatet ProductsDataTable i lokala variabler.

Webbplatskartans konstruktion börjar med att skapa rotnoden och tilldela den till root. Överlagringen av konstruktornSiteMapNode som används här och i det här BuildSiteMap sammanhanget skickas följande information:

  • En referens till webbplatsöversiktsprovidern (this).
  • SiteMapNode s Key. Det här obligatoriska värdet måste vara unikt för varje SiteMapNode.
  • SiteMapNode s Url. Url är valfritt, men om det anges måste varje SiteMapNode värde Url vara unikt.
  • s SiteMapNodeTitle, som krävs.

Metodanropet AddNode(root) lägger till SiteMapNoderoot till webbplatskartan som roten. Nästa, varje ProductRow i ProductsDataTable räknas upp. Om det redan finns en SiteMapNode för den aktuella produktkategorin refereras den till. Annars skapas en ny SiteMapNode för kategorin och läggs som underordnad till SiteMapNode``root genom metodanropet AddNode(categoryNode, root). När lämplig kategorinod SiteMapNode har hittats eller skapats skapas en SiteMapNode för den aktuella produkten och läggs till som underordnad i kategorin SiteMapNode via AddNode(productNode, categoryNode). Observera att kategorins SiteMapNode egenskapsvärde Url är ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID när produktegenskapen SiteMapNode tilldelas Url~/SiteMapNode/ProductDetails.aspx?ProductID=productID .

Anmärkning

De produkter som har ett databasvärde NULL för sina CategoryID grupperas under en kategori SiteMapNode vars Title egenskap är inställd på Ingen och vars Url egenskap är inställd på en tom sträng. Jag bestämde mig för att ställa in Url på en tom sträng eftersom ProductBLL klassens GetProductsByCategory(categoryID) metod för närvarande saknar möjligheten att returnera bara de produkter med ett NULLCategoryID värde. Dessutom ville jag visa hur navigeringskontrollerna återge en SiteMapNode som saknar ett värde för sin Url egenskap. Jag rekommenderar att du utökar den här handledningen så att egenskapen None SiteMapNodeUrl pekar på ProductsByCategory.aspx, men bara visar produkterna med NULLCategoryID värdena.

När du har skapat webbplatsöversikten läggs ett godtyckligt objekt till i datacachen med hjälp av ett SQL-cacheberoende för tabellerna Categories och Products via ett AggregateCacheDependency objekt. Vi utforskade att använda SQL Cache-beroenden i föregående självstudie, Använda SQL Cache-beroenden. Den anpassade webbplatsöversiktsprovidern använder dock en överbelastning av datacachens Insert metod som vi ännu inte har utforskat. Den här överbelastningen accepterar en delegering som sin sista indataparameter, vilken anropas när objektet tas bort från cacheminnet. Mer specifikt skickar vi in ett nytt CacheItemRemovedCallback ombud som pekar på metoden OnSiteMapChanged som definieras längre ned i NorthwindSiteMapProvider klassen.

Anmärkning

Minnesintern representation av platskartan cachelagras via variabeln rootpå klassnivå . Eftersom det bara finns en instans av den anpassade webbplatskartans providerklass och eftersom den instansen delas mellan alla trådar i webbprogrammet fungerar den här klassvariabeln som en cache. Metoden BuildSiteMap använder också datacachen, men bara som ett sätt att ta emot meddelanden när underliggande databasdata i tabellerna Categories eller Products ändras. Observera att värdet som placeras i datacachen bara är aktuellt datum och tid. De faktiska webbplatsöversiktsdata placeras inte i datacachen.

Metoden BuildSiteMap slutförs genom att returnera rotnoden för platskartan.

De återstående metoderna är ganska enkla. GetRootNodeCore ansvarar för att returnera rotnoden. Eftersom BuildSiteMap returnerar roten GetRootNodeCore returnerar BuildSiteMap bara returvärdet. Metoden OnSiteMapChanged återgår root till null när cacheobjektet tas bort. När roten har återställts till null, kommer platskartan att byggas om nästa gång BuildSiteMap anropas. Slutligen CachedDate returnerar egenskapen det datum- och tidsvärde som lagras i datacachen, om det finns ett sådant värde. Den här egenskapen kan användas av en sidutvecklare för att avgöra när webbplatsöversiktsdata senast cachelagrades.

Steg 7: Registrera NorthwindSiteMapProvider

För att vår webbapp ska kunna använda webbplatsöversiktsprovidern NorthwindSiteMapProvider som skapades i steg 6 måste vi registrera den <siteMap> i avsnittet Web.config. Mer specifikt lägger du till följande markering i elementet <system.web> i Web.config:

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

Den här markeringen gör två saker: för det första anger den att den inbyggda AspNetXmlSiteMapProvider är standardprovidern för webbplatskartor. För det andra registrerar den anpassade webbplatskartaprovidern som skapades i steg 6 med det människovänliga namnet Northwind .

Anmärkning

För webbplatsöversiktsprovidrar som finns i programmets App_Code mapp är värdet för type attributet helt enkelt klassnamnet. Alternativt kan den anpassade webbplatsöversiktsprovidern ha skapats i ett separat klassbiblioteksprojekt med den kompilerade sammansättningen placerad i webbprogrammets /Bin katalog. I så fall type skulle attributvärdet vara Namnområde.ClassName, AssemblyName .

När du har uppdaterat Web.config, ta en stund för att visa vilken sida som helst från handledningarna i en webbläsare. Observera att navigeringsgränssnittet till vänster fortfarande visar de avsnitt och handledningar som definierats i Web.sitemap. Det beror på att vi lämnade AspNetXmlSiteMapProvider som standardprovider. För att kunna skapa ett navigeringsgränssnittselement som använder NorthwindSiteMapProvidermåste vi uttryckligen ange att northwind-webbplatsöversiktsprovidern ska användas. Vi får se hur du gör detta i steg 8.

Steg 8: Visa webbplatsöversiktsinformation med hjälp av den anpassade webbplatsöversiktsprovidern

När den anpassade webbplatsöversiktsprovidern har skapats och registrerats i Web.configär vi redo att lägga till navigeringskontroller till Default.aspxsidorna , ProductsByCategory.aspxoch ProductDetails.aspx i SiteMapProvider mappen. Börja med att öppna sidan Default.aspx och dra en SiteMapPath från verktygslådan till designern. Kontrollen SiteMapPath finns i avsnittet Navigering i verktygslådan.

Lägga till en SiteMapPath i Default.aspx

Bild 16: Lägg till en SiteMapPath i Default.aspx (Klicka om du vill visa en bild i full storlek)

Kontrollen SiteMapPath visar en sökväg som anger den aktuella sidans plats på webbplatskartan. Vi har lagt till en SiteMapPath överst på master page i handledningen Huvudsidor och Webbplatsnavigering.

Ta en stund och titta på den här sidan via en webbläsare. SiteMapPath som lades till i bild 16 använder standardprovidern för webbplatskartor och hämtar sina data från Web.sitemap. Därför visar brödsmulan Hem > Anpassa webbplatskartan, precis som brödsmulan i det övre högra hörnet.

Breadcrumb använder standardprovidern för webbplatsöversikt

Bild 17: Breadcrumb använder standardprovidern för webbplatskartor (Klicka om du vill visa en bild i full storlek)

Om du vill att SiteMapPath ska läggas till i bild 16 använder du den anpassade webbplatsöversiktsprovidern som vi skapade i steg 6. Ange dessSiteMapProvider egenskap till Northwind, namnet som vi tilldelade i NorthwindSiteMapProviderWeb.config. Tyvärr fortsätter designern att använda standardprovidern för webbplatsöversikt, men om du besöker sidan via en webbläsare när du har gjort den här egenskapsändringen ser du att sökvägsuppsättningen nu använder den anpassade webbplatskartprovidern.

Skärmbild som visar hur sökväg visar den anpassade webbplatsöversiktsprovidern.

Bild 18: Breadcrumb använder nu den anpassade webbplatskartleverantören NorthwindSiteMapProvider (klicka för att visa bilden i full storlek)

Kontrollen SiteMapPath visar ett mer funktionellt användargränssnitt på ProductsByCategory.aspx sidorna och ProductDetails.aspx . Lägg till en SiteMapPath på dessa sidor och ställ in SiteMapProvider-egenskapen till Northwind i båda. Klicka Default.aspx på länken Visa produkter för Drycker och sedan på länken Visa information för Chai Tea. Som bild 19 visar innehåller navigationsstigen det aktuella avsnittet av webbplatskartan (Chai Tea) och dess överordnade kategorier: Drycker och Alla kategorier.

Skärmbild som visar hur sökväg visar det aktuella webbplatskartavsnittet (Chai Tea) och dess förfäder (drycker och alla kategorier).

Bild 19: Breadcrumb använder nu den anpassade webbplatskarteprovidern NorthwindSiteMapProvider (klicka om du vill visa en bild i full storlek)

Andra gränssnittselement för navigeringsanvändare kan användas utöver SiteMapPath, till exempel kontrollerna Meny och TreeView. Sidorna Default.aspx, ProductsByCategory.aspx och ProductDetails.aspx i nedladdningen för den här självstudien inkluderar, till exempel, alla menykontroller (se bild 20). Se ASP.NET 2.0:s avancerade webbplatsnavigeringsfunktioner och avsnittet Använda platsnavigeringskontroller i snabbstarterna ASP.NET 2.0 för en mer djupgående titt på navigeringskontrollerna och platskartsystemet i ASP.NET 2.0.

Menykontrollen visar var och en av kategorierna och produkterna

Bild 20: Menykontrollen visar var och en av kategorierna och produkterna (klicka om du vill visa en bild i full storlek)

Som tidigare nämnts i den här självstudien kan webbplatskartstrukturen nås programmatiskt via SiteMap klassen. Följande kod returnerar standardproviderns rot SiteMapNode :

SiteMapNode root = SiteMap.RootNode;

Eftersom AspNetXmlSiteMapProvider är standardprovidern för vårt program, skulle koden ovan returnera rotnoden som definierats i Web.sitemap. Om du vill referera till en annan webbplatskartleverantör än standardvärdet använder du SiteMapklassensProviders egenskap så här:

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

Där namnet är namnet på den anpassade webbplatsöversiktsprovidern ( Northwind, för vårt webbprogram).

För att komma åt en medlem som är specifik för en sitemap-leverantör, använd SiteMap.Providers["name"] för att hämta leverantörsinstansen och omvandla den sedan till rätt typ. Om du till exempel vill visa NorthwindSiteMapProvider egenskapen s CachedDate på en ASP.NET-sida använder du följande kod:

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!";
}

Anmärkning

Se till att testa sql-cacheberoendefunktionen. När du har besökt Default.aspxsidorna, ProductsByCategory.aspx och ProductDetails.aspx går du till en av handledningarna i avsnittet Redigera, Infoga och Ta bort och ändrar namnet på en kategori eller produkt. Gå sedan tillbaka till en av sidorna i SiteMapProvider mappen. Om det har gått tillräckligt med tid för avsökningsmekanismen att notera ändringen av den underliggande databasen, bör webbplatskartan uppdateras för att visa det nya produkt- eller kategorinamnet.

Sammanfattning

ASP.NET webbplatsöversiktsfunktioner på 2,0 s innehåller en SiteMap klass, ett antal inbyggda navigeringswebbkontroller och en standardleverantör för webbplatsöversikt som förväntar sig att webbplatsöversiktsinformationen sparas i en XML-fil. För att kunna använda webbplatsöversiktsinformation från någon annan källa, till exempel från en databas, programmets arkitektur eller en fjärrwebbtjänst, måste vi skapa en anpassad webbplatsöversiktsprovider. Det innebär att skapa en klass som direkt eller indirekt härleds från SiteMapProvider klassen.

I den här handledningen har vi sett hur du skapar en anpassad webbplatskartleverantör som baserar webbplatskartan på produkt- och kategoriinformation som har hämtats från programarkitekturen. Vår provider utökade StaticSiteMapProvider klassen och innebar att skapa en BuildSiteMap metod som hämtade data, konstruerade platskartehierarkin och cachelagrade den resulterande strukturen i en variabel på klassnivå. Vi använde ett SQL-cacheberoende med en återanropsfunktion för att ogiltigförklara den cachelagrade strukturen när underliggande Categories data eller Products data ändras.

Lycka till med programmerandet!

Ytterligare läsning

Mer information om de ämnen som beskrivs i den här självstudien finns i följande resurser:

Om författaren

Scott Mitchell, författare till sju ASP/ASP.NET-böcker och grundare av 4GuysFromRolla.com, har arbetat med Microsofts webbtekniker sedan 1998. Scott arbetar som oberoende konsult, tränare och författare. Hans senaste bok är Sams Teach Yourself ASP.NET 2.0 på 24 timmar. Han kan nås på mitchell@4GuysFromRolla.com.

Särskilt tack till

Den här självstudieserien granskades av många användbara granskare. Huvudgranskare för den här självstudien var Dave Gardner, Zack Jones, Teresa Murphy och Bernadette Leigh. Vill du granska mina kommande MSDN-artiklar? Om så är fallet, hör av dig på mitchell@4GuysFromRolla.com.