Partilhar via


Criação de um provedor de mapa de site personalizado controlado por banco de dados (C#)

por Scott Mitchell

Baixar PDF

O provedor de mapa de site padrão no ASP.NET 2.0 recupera seus dados de um arquivo XML estático. Embora o provedor baseado em XML seja adequado para muitos sites de pequeno e médio porte, aplicativos Web maiores exigem um mapa de site mais dinâmico. Neste tutorial, criaremos um provedor de mapa de site personalizado que recupera seus dados da Camada de Lógica de Negócios, que, por sua vez, recupera dados do banco de dados.

Introdução

ASP.NET recurso de mapa de site 2.0 permite que um desenvolvedor de página defina um mapa de site de um aplicativo Web em algum meio persistente, como em um arquivo XML. Depois de definidos, os dados do mapa do site podem ser acessados programaticamente por meio da SiteMap classe no System.Web namespace ou por meio de uma variedade de controles da Web de navegação, como os controles SiteMapPath, Menu e TreeView. O sistema de mapa de sites usa o modelo de provedor para que diferentes implementações de serialização de mapa de site possam ser criadas e conectadas a um aplicativo Web. O provedor de mapa de site padrão fornecido com ASP.NET 2.0 persiste a estrutura do mapa do site em um arquivo XML. No tutorial Páginas Mestras e Navegação no Site , criamos um arquivo chamado Web.sitemap que continha essa estrutura e atualizamos seu XML a cada nova seção de tutorial.

O provedor de mapa de site baseado em XML padrão funcionará bem se a estrutura do mapa do site for bastante estática, como para esses tutoriais. Em muitos cenários, no entanto, um mapa de site mais dinâmico é necessário. Considere o mapa do site mostrado na Figura 1, em que cada categoria e produto aparecem como seções na estrutura do site. Com este mapa do site, visitar a página da Web correspondente ao nó raiz pode listar todas as categorias, enquanto visitar uma página da Web de determinada categoria listaria os produtos da categoria e a exibição de uma página da Web de um produto específico mostraria os detalhes desse produto.

As categorias e produtos compõem a estrutura do Mapa do Site

Figura 1: As categorias e produtos que compõem a estrutura do Mapa do Site (clique para exibir a imagem em tamanho real)

Embora essa estrutura baseada em categoria e produto possa ser codificada no Web.sitemap arquivo, o arquivo precisaria ser atualizado sempre que uma categoria ou produto fosse adicionado, removido ou renomeado. Consequentemente, a manutenção do mapa do site seria muito simplificada se sua estrutura fosse recuperada do banco de dados ou, idealmente, da Camada Lógica de Negócios da arquitetura do aplicativo. Dessa forma, à medida que produtos e categorias fossem adicionados, renomeados ou excluídos, o mapa do site seria atualizado automaticamente para refletir essas alterações.

Como ASP.NET serialização de mapa de site 2.0 é criada em cima do modelo de provedor, podemos criar nosso próprio provedor de mapa de site personalizado que captura seus dados de um armazenamento de dados alternativo, como o banco de dados ou a arquitetura. Neste tutorial, criaremos um provedor personalizado que recupera seus dados da BLL. Vamos começar!

Observação

O provedor de mapa de site personalizado criado neste tutorial está firmemente acoplado à arquitetura e ao modelo de dados do aplicativo. Os artigos Armazenando Mapas de Sites do Jeff Prosise em SQL Server e o Provedor de Mapa de Site do SQL que você estava esperando examinam uma abordagem generalizada para armazenar dados do mapa do site em SQL Server.

Etapa 1: Criando as páginas da Web do provedor de mapa de site personalizado

Antes de começarmos a criar um provedor de mapa de site personalizado, vamos primeiro adicionar as páginas ASP.NET que precisaremos para este tutorial. Comece adicionando uma nova pasta chamada SiteMapProvider. Em seguida, adicione as seguintes páginas ASP.NET a essa pasta, certificando-se de associar cada página à Site.master página master:

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

Adicione também uma CustomProviders subpasta à App_Code pasta .

Adicionar as páginas de ASP.NET para os Tutoriais de Provider-Related do Mapa do Site

Figura 2: Adicionar as páginas de ASP.NET para os Tutoriais de Provider-Related do Mapa do Site

Como há apenas um tutorial para esta seção, não precisamos Default.aspx listar os tutoriais da seção. Em vez disso, Default.aspx exibirá as categorias em um controle GridView. Abordaremos isso na Etapa 2.

Em seguida, atualize Web.sitemap para incluir uma referência à Default.aspx página. Especificamente, adicione a seguinte marcação após o cache <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." />

Depois de atualizar Web.sitemap, reserve um momento para exibir o site de tutoriais por meio de um navegador. O menu à esquerda agora inclui um item para o tutorial do provedor de mapa de site exclusivo.

O mapa do site agora inclui uma entrada para o Tutorial do Provedor de Mapa do Site

Figura 3: o mapa do site agora inclui uma entrada para o Tutorial do Provedor de Mapa do Site

Este tutorial main foco é ilustrar a criação de um provedor de mapa de site personalizado e a configuração de um aplicativo Web para usar esse provedor. Em particular, criaremos um provedor que retorna um mapa de site que inclui um nó raiz junto com um nó para cada categoria e produto, conforme descrito na Figura 1. Em geral, cada nó no mapa do site pode especificar uma URL. Para o mapa do site, a URL do nó raiz será ~/SiteMapProvider/Default.aspx, que listará todas as categorias no banco de dados. Cada nó de categoria no mapa do site terá uma URL que aponta para ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID, que listará todos os produtos na categoryID especificada. Por fim, cada nó de mapa do site do produto apontará para ~/SiteMapProvider/ProductDetails.aspx?ProductID=productID, que exibirá os detalhes específicos do produto.

Para começar, precisamos criar as Default.aspxpáginas , ProductsByCategory.aspxe ProductDetails.aspx . Essas páginas são concluídas nas Etapas 2, 3 e 4, respectivamente. Como o impulso deste tutorial está nos provedores de mapa do site e, como tutoriais anteriores abordaram a criação desses tipos de relatórios de master/detalhes de várias páginas, vamos nos apressar nas Etapas 2 a 4. Se você precisar de um atualizador para criar relatórios de master/detalhes que abrangem várias páginas, consulte o tutorial Filtragem mestre/detalhe entre duas páginas.

Etapa 2: Exibindo uma lista de categorias

Abra a Default.aspx página na SiteMapProvider pasta e arraste um GridView da Caixa de Ferramentas para o Designer, definindo-a ID como Categories. Na marca inteligente GridView, associe-a a um novo ObjectDataSource chamado CategoriesDataSource e configure-o para que ele recupere seus dados usando o CategoriesBLL método da classe s GetCategories . Como esse GridView apenas exibe as categorias e não fornece recursos de modificação de dados, defina as listas suspensas nas guias UPDATE, INSERT e DELETE como (Nenhum) .

Configurar o ObjectDataSource para retornar categorias usando o método GetCategories

Figura 4: configurar o ObjectDataSource para retornar categorias usando o GetCategories método (clique para exibir a imagem em tamanho real)

Defina o Drop-Down Listas nas guias UPDATE, INSERT e DELETE como (Nenhum)

Figura 5: defina o Drop-Down Listas nas guias UPDATE, INSERT e DELETE como (Nenhum) (Clique para exibir a imagem em tamanho real)

Depois de concluir o assistente Configurar Fonte de Dados, o Visual Studio adicionará um BoundField para CategoryID, CategoryName, Description, NumberOfProductse BrochurePath. Edite o GridView para que ele contenha apenas o CategoryName e Description BoundFields e atualize a CategoryName propriedade BoundField para HeaderText Categoria .

Em seguida, adicione um HyperLinkField e posicione-o para que ele seja o campo mais à esquerda. Defina a DataNavigateUrlFields propriedade como CategoryID e a DataNavigateUrlFormatString propriedade como ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}. Defina a Text propriedade como Exibir Produtos .

Adicionar um HyperLinkField ao GridView de Categorias

Figura 6: Adicionar um HyperLinkField ao Categories GridView

Depois de criar o ObjectDataSource e personalizar os campos do GridView, a marcação declarativa de dois controles terá a seguinte aparência:

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

A Figura 7 mostra Default.aspx quando exibida por meio de um navegador. Clicar no link Exibir Produtos de uma categoria leva você ao ProductsByCategory.aspx?CategoryID=categoryID, que criaremos na Etapa 3.

Cada categoria é listada junto com um link exibir produtos

Figura 7: cada categoria é listada junto com um link Exibir Produtos (clique para exibir imagem em tamanho real)

Etapa 3: Listar os produtos das categorias selecionadas

Abra a página e adicione um GridView, nomeando-o ProductsByCategory.aspxProductsByCategorycomo . De sua marca inteligente, associe GridView a um novo ObjectDataSource chamado ProductsByCategoryDataSource. Configure o ObjectDataSource para usar o ProductsBLL método da classe s GetProductsByCategoryID(categoryID) e defina as listas suspensas como (Nenhum) nas guias UPDATE, INSERT e DELETE.

Usar o método GetProductsByCategoryID(categoryID) da classe ProductsBLL

Figura 8: usar o ProductsBLL método classe s GetProductsByCategoryID(categoryID) (clique para exibir imagem em tamanho real)

A etapa final no assistente Configurar Fonte de Dados solicita uma fonte de parâmetro para categoryID. Como essas informações são passadas pelo campo CategoryIDquerystring , selecione QueryString na lista suspensa e insira CategoryID na caixa de texto QueryStringField, conforme mostrado na Figura 9. Clique em Concluir para concluir o assistente.

Usar o campo Querystring CategoryID para o parâmetro categoryID

Figura 9: usar o CategoryID campo Querystring para o parâmetro categoryID (clique para exibir a imagem em tamanho real)

Depois de concluir o assistente, o Visual Studio adicionará BoundFields correspondentes e um CheckBoxField ao GridView para os campos de dados do produto. Remova todos, exceto o ProductName, UnitPricee SupplierName BoundFields. Personalize essas três propriedades BoundFields HeaderText para ler Produto, Preço e Fornecedor, respectivamente. Formate o UnitPrice BoundField como uma moeda.

Em seguida, adicione um HyperLinkField e mova-o para a posição mais à esquerda. Defina sua Text propriedade como Exibir Detalhes, sua DataNavigateUrlFields propriedade como ProductIDe sua DataNavigateUrlFormatString propriedade como ~/SiteMapProvider/ProductDetails.aspx?ProductID={0}.

Adicionar um HyperLinkField de detalhes de exibição que aponta para ProductDetails.aspx

Figura 10: Adicionar um HyperLinkField de detalhes de exibição para o qual aponta ProductDetails.aspx

Depois de fazer essas personalizações, a marcação declarativa de GridView e ObjectDataSource deve ser semelhante à seguinte:

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

Volte a exibir Default.aspx por meio de um navegador e clique no link Exibir Produtos para Bebidas. Isso levará você ao ProductsByCategory.aspx?CategoryID=1, exibindo os nomes, os preços e os fornecedores dos produtos no banco de dados Northwind que pertencem à categoria Bebidas (consulte a Figura 11). Fique à vontade para aprimorar ainda mais esta página para incluir um link para retornar usuários à página de listagem de categorias (Default.aspx) e um controle DetailsView ou FormView que exibe o nome e a descrição da categoria selecionada.

Os nomes, preços e fornecedores de bebidas são exibidos

Figura 11: Os nomes, os preços e os fornecedores de bebidas são exibidos (clique para exibir a imagem em tamanho real)

Etapa 4: Mostrando detalhes de um produto

A página final, ProductDetails.aspx, exibe os detalhes dos produtos selecionados. Abra ProductDetails.aspx e arraste um DetailsView da Caixa de Ferramentas para o Designer. Defina a propriedade DetailsView como IDProductInfo e desmarque seus Height valores de propriedade e Width . De sua marca inteligente, associe DetailsView a um novo ObjectDataSource chamado ProductDataSource, configurando ObjectDataSource para extrair seus dados do ProductsBLL método da classe s GetProductByProductID(productID) . Assim como nas páginas da Web anteriores criadas nas Etapas 2 e 3, defina as listas suspensas nas guias UPDATE, INSERT e DELETE como (Nenhum) .

Configurar o ObjectDataSource para usar o método GetProductByProductID(productID)

Figura 12: configurar o ObjectDataSource para usar o GetProductByProductID(productID) método (clique para exibir a imagem em tamanho real)

A última etapa do assistente Configurar Fonte de Dados solicita a origem do parâmetro productID . Como esses dados vêm por meio do campo ProductIDquerystring , defina a lista suspensa como QueryString e a caixa de texto QueryStringField como ProductID. Por fim, clique no botão Concluir para concluir o assistente.

Configurar o parâmetro productID para efetuar pull de seu valor do campo querystring ProductID

Figura 13: configurar o parâmetro productID para efetuar pull de seu valor do ProductID campo Querystring (clique para exibir a imagem em tamanho real)

Depois de concluir o assistente Configurar Fonte de Dados, o Visual Studio criará BoundFields correspondentes e um CheckBoxField no DetailsView para os campos de dados do produto. Remova , ProductIDSupplierIDe CategoryID BoundFields e configure os campos restantes conforme você achar adequado. Depois de algumas configurações estéticas, minha marcação declarativa de DetailsView e ObjectDataSource se parecia com o seguinte:

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

Para testar esta página, volte para Default.aspx e clique em Exibir Produtos para a categoria Bebidas. Na listagem de produtos de bebidas, clique no link Exibir Detalhes do Chai Tea. Isso levará você para ProductDetails.aspx?ProductID=1, que mostra os detalhes do Chai Tea (consulte a Figura 14).

Chai Tea s Supplier, Category, Price, and Other Information is Displayed

Figura 14: Chai Tea s Supplier, Category, Price, and Other Information is Displayed (Click to view full-size image)

Etapa 5: Noções básicas sobre o funcionamento interno de um provedor de mapa de site

O mapa do site é representado na memória do servidor Web como uma coleção de SiteMapNode instâncias que formam uma hierarquia. Deve haver exatamente uma raiz, todos os nós não raiz devem ter exatamente um nó pai, e todos os nós podem ter um número arbitrário de filhos. Cada SiteMapNode objeto representa uma seção na estrutura do site; essas seções geralmente têm uma página da Web correspondente. Consequentemente, a SiteMapNode classe tem propriedades como Title, Urle Description, que fornecem informações para a seção que o SiteMapNode representa. Há também uma propriedade que identifica exclusivamente cada uma Key na hierarquia, bem como as propriedades usadas para estabelecer essa hierarquia ChildNodes, ParentNode, NextSibling, PreviousSiblinge assim por SiteMapNode diante.

A Figura 15 mostra a estrutura geral do mapa do site da Figura 1, mas com os detalhes da implementação esboçados em detalhes mais finos.

Cada SiteMapNode tem propriedades como Título, URL, Chave e Assim Por Diante

Figura 15: Cada SiteMapNode uma tem propriedades como Title, Url, Keye Assim por diante (clique para exibir a imagem em tamanho real)

O mapa do site é acessível por meio da SiteMap classe no System.Web namespace. Essa propriedade da RootNode classe retorna a instância raiz SiteMapNode do mapa do site; CurrentNode retorna a SiteMapNode cuja Url propriedade corresponde à URL da página solicitada no momento. Essa classe é usada internamente por ASP.NET controles Web de navegação 2.0.

Quando as SiteMap propriedades da classe s são acessadas, ela deve serializar a estrutura do mapa do site de algum meio persistente na memória. No entanto, a lógica de serialização do mapa do site não é codificada na SiteMap classe . Em vez disso, em runtime, a SiteMap classe determina qual provedor de mapa de site usar para serialização. Por padrão, a XmlSiteMapProvider classe é usada, que lê a estrutura do mapa do site de um arquivo XML formatado corretamente. No entanto, com um pouco de trabalho, podemos criar nosso próprio provedor de mapa de site personalizado.

Todos os provedores de mapa de site devem ser derivados da SiteMapProvider classe , que inclui os métodos e propriedades essenciais necessários para provedores de mapa de site, mas omite muitos dos detalhes da implementação. Uma segunda classe, StaticSiteMapProvider, estende a SiteMapProvider classe e contém uma implementação mais robusta da funcionalidade necessária. Internamente, o armazena as SiteMapNode instâncias do mapa do site em um Hashtable e fornece métodos como AddNode(child, parent), RemoveNode(siteMapNode), e Clear() que adicionam e removem SiteMapNode s ao interno Hashtable.StaticSiteMapProvider XmlSiteMapProvider é derivado de StaticSiteMapProvider.

Ao criar um provedor de mapa de site personalizado que estende , há dois métodos StaticSiteMapProviderabstratos que devem ser substituídos: BuildSiteMap e GetRootNodeCore. BuildSiteMap, como o nome indica, é responsável por carregar a estrutura do mapa do site do armazenamento persistente e construí-la na memória. GetRootNodeCore retorna o nó raiz no mapa do site.

Antes que um aplicativo Web possa usar um provedor de mapa de site, ele deve ser registrado na configuração do aplicativo. Por padrão, a XmlSiteMapProvider classe é registrada usando o nome AspNetXmlSiteMapProvider. Para registrar provedores de mapa de site adicionais, adicione a seguinte marcação a Web.config:

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

O valor do nome atribui um nome legível por humanos ao provedor enquanto o tipo especifica o nome de tipo totalmente qualificado do provedor de mapa do site. Exploraremos valores concretos para os valores de nome e tipo na Etapa 7, depois de criarmos nosso provedor de mapa de site personalizado.

A classe de provedor de mapa do site é instanciada na primeira vez que é acessada da SiteMap classe e permanece na memória durante o tempo de vida do aplicativo Web. Como há apenas uma instância do provedor de mapa do site que pode ser invocada de vários visitantes simultâneos do site, é imperativo que os métodos do provedor sejam thread-safe.

Por motivos de desempenho e escalabilidade, é importante armazenar em cache a estrutura do mapa do site na memória e retornar essa estrutura armazenada em cache em vez de recriá-la sempre que o BuildSiteMap método for invocado. BuildSiteMap pode ser chamado várias vezes por solicitação de página por usuário, dependendo dos controles de navegação em uso na página e da profundidade da estrutura do mapa do site. De qualquer forma, se não armazenarmos em cache a estrutura do mapa do site em BuildSiteMap , sempre que ela for invocada, precisaremos recuperar novamente as informações de produto e categoria da arquitetura (o que resultaria em uma consulta para o banco de dados). Como discutimos nos tutoriais de cache anteriores, os dados armazenados em cache podem ficar obsoletos. Para combater isso, podemos usar expirações baseadas em dependência de cache SQL ou tempo.

Observação

Opcionalmente, um provedor de mapa de site pode substituir o Initialize método . Initialize é invocado quando o provedor de mapa do site é instanciado pela primeira vez e é passado todos os atributos personalizados atribuídos ao provedor no Web.config no <add> elemento como: <add name="name" type="type" customAttribute="value" />. É útil se você quiser permitir que um desenvolvedor de página especifique várias configurações relacionadas ao provedor de mapa de site sem precisar modificar o código do provedor. Por exemplo, se estivéssemos lendo os dados de categoria e produtos diretamente do banco de dados em vez de por meio da arquitetura, provavelmente gostaríamos de permitir que o desenvolvedor da página especifique o banco de dados cadeia de conexão em Web.config vez de usar um valor embutido em código no código do provedor. O provedor de mapa de site personalizado que criaremos na Etapa 6 não substitui esse Initialize método. Para obter um exemplo de como usar o Initialize método , consulte Jeff Prosise'sStoring Site Maps in SQL Server artigo.

Etapa 6: Criando o provedor de mapa de site personalizado

Para criar um provedor de mapa de site personalizado que compila o mapa do site a partir das categorias e produtos no banco de dados Northwind, precisamos criar uma classe que estenda StaticSiteMapProvider. Na Etapa 1, pedi que você adicionasse uma CustomProviders pasta na App_Code pasta – adicione uma nova classe a essa pasta chamada NorthwindSiteMapProvider. Adicione o código a seguir à classe 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?;
        }
    }
}

Vamos começar explorando esse método de classe, BuildSiteMap que começa com uma lock instrução . A lock instrução só permite que um thread de cada vez insira, serializando assim o acesso ao código e impedindo que dois threads simultâneos pisassem um no outro.

A variável root de nível SiteMapNode de classe é usada para armazenar em cache a estrutura do mapa do site. Quando o mapa do site for construído pela primeira vez ou pela primeira vez após a modificação dos dados subjacentes, root será null e a estrutura do mapa do site será construída. O nó raiz do mapa do site é atribuído a durante o processo de construção para root que, na próxima vez que esse método for chamado, root não será null. Consequentemente, desde root que a estrutura do mapa do site não null seja retornada ao chamador sem precisar recriá-la.

Se root for null, a estrutura do mapa do site será criada com base nas informações do produto e da categoria. O mapa do site é criado criando as SiteMapNode instâncias e, em seguida, formando a hierarquia por meio de chamadas para o StaticSiteMapProvider método da classe s AddNode . AddNode executa a contabilidade interna, armazenando as instâncias sortidas SiteMapNode em um Hashtable. Antes de começarmos a construir a hierarquia, começamos chamando o Clear método , que limpa os elementos do interno Hashtable. Em seguida, o ProductsBLL método da classe s GetProducts e o resultado ProductsDataTable são armazenados em variáveis locais.

A construção do mapa do local começa criando o nó raiz e atribuindo-o a root. A sobrecarga do SiteMapNode construtor s usada aqui e em todo este BuildSiteMap é passada as seguintes informações:

  • Uma referência ao provedor de mapa do site (this).
  • Os SiteMapNode s Key. Esse valor necessário deve ser exclusivo para cada SiteMapNode.
  • Os SiteMapNode s Url. Url é opcional, mas, se fornecido, cada SiteMapNode valor deve Url ser exclusivo.
  • O SiteMapNode s Title, que é necessário.

A AddNode(root) chamada de método adiciona o SiteMapNoderoot ao mapa do site como a raiz. Em seguida, cada ProductRow um ProductsDataTable no é enumerado. Se já existir um SiteMapNode para a categoria do produto atual, ele será referenciado. Caso contrário, um novo SiteMapNode para a categoria será criado e adicionado como um filho do SiteMapNode``root por meio da chamada de AddNode(categoryNode, root) método. Depois que o nó de categoria SiteMapNode apropriado for encontrado ou criado, um SiteMapNode será criado para o produto atual e adicionado como um filho da categoria SiteMapNode por meio AddNode(productNode, categoryNode)de . Observe que o valor da propriedade da categoria SiteMapNode é ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID enquanto a propriedade do Url produto SiteMapNode é atribuída ~/SiteMapNode/ProductDetails.aspx?ProductID=productID.Url

Observação

Os produtos que têm um valor de banco de dados NULL para eles CategoryID são agrupados em uma categoria SiteMapNode cuja Title propriedade é definida como None e cuja Url propriedade é definida como uma cadeia de caracteres vazia. Decidi definir Url como uma cadeia de caracteres vazia, pois o ProductBLL método da GetProductsByCategory(categoryID) classe atualmente não tem a capacidade de retornar apenas esses produtos com um NULLCategoryID valor. Além disso, eu queria demonstrar como os controles de navegação renderizam um SiteMapNode que não tem um valor para sua Url propriedade. Eu encorajo você a estender este tutorial para que a propriedade None SiteMapNodeUrl aponte para ProductsByCategory.aspx, mas exibe apenas os produtos com NULLCategoryID valores.

Depois de construir o mapa do site, um objeto arbitrário é adicionado ao cache de dados usando uma dependência de cache SQL nas Categories tabelas e Products por meio de um AggregateCacheDependency objeto . Exploramos o uso de dependências de cache do SQL no tutorial anterior, Usando dependências de cache do SQL. No entanto, o provedor de mapa de site personalizado usa uma sobrecarga do método s Insert do cache de dados que ainda não exploramos. Essa sobrecarga aceita como parâmetro de entrada final um delegado que é chamado quando o objeto é removido do cache. Especificamente, passamos um novo CacheItemRemovedCallback delegado que aponta para o OnSiteMapChanged método definido mais abaixo na NorthwindSiteMapProvider classe .

Observação

A representação na memória do mapa do site é armazenada em cache por meio da variável rootde nível de classe . Como há apenas uma instância da classe de provedor de mapa de site personalizada e, como essa instância é compartilhada entre todos os threads no aplicativo Web, essa variável de classe serve como um cache. O BuildSiteMap método também usa o cache de dados, mas apenas como um meio de receber notificação quando os dados de banco de dados subjacentes nas Categories tabelas ou Products são alterados. Observe que o valor colocado no cache de dados é apenas a data e a hora atuais. Os dados reais do mapa do site não são colocados no cache de dados.

O BuildSiteMap método é concluído retornando o nó raiz do mapa do site.

Os métodos restantes são bastante simples. GetRootNodeCore é responsável por retornar o nó raiz. Como BuildSiteMap retorna a raiz, GetRootNodeCore simplesmente retorna BuildSiteMap o valor retornado de . O OnSiteMapChanged método define root de volta para null quando o item de cache é removido. Com a raiz definida nullcomo , na próxima vez BuildSiteMap que for invocada, a estrutura do mapa do site será recriada. Por fim, a CachedDate propriedade retornará o valor de data e hora armazenado no cache de dados, se esse valor existir. Essa propriedade pode ser usada por um desenvolvedor de páginas para determinar quando os dados do mapa do site foram armazenados em cache pela última vez.

Etapa 7: Registrando oNorthwindSiteMapProvider

Para que nosso aplicativo Web use o provedor de mapa do NorthwindSiteMapProvider site criado na Etapa 6, precisamos registrá-lo na <siteMap> seção do Web.config. Especificamente, adicione a seguinte marcação dentro do <system.web> elemento em Web.config:

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

Essa marcação faz duas coisas: primeiro, indica que o interno é o provedor de mapa de site padrão; segundo AspNetXmlSiteMapProvider , ele registra o provedor de mapa de site personalizado criado na Etapa 6 com o nome amigável ao ser humano Northwind .

Observação

Para provedores de mapa de site localizados na pasta do App_Code aplicativo, o valor do type atributo é simplesmente o nome da classe. Como alternativa, o provedor de mapa de site personalizado poderia ter sido criado em um projeto separado da Biblioteca de Classes com o assembly compilado colocado no diretório do /Bin aplicativo Web. Nesse caso, o valor do type atributo seria Namespace.ClassName, AssemblyName .

Depois de atualizar Web.config, reserve um momento para exibir qualquer página dos tutoriais em um navegador. Observe que a interface de navegação à esquerda ainda mostra as seções e tutoriais definidos em Web.sitemap. Isso ocorre porque saímos AspNetXmlSiteMapProvider como o provedor padrão. Para criar um elemento de interface do usuário de navegação que usa o NorthwindSiteMapProvider, precisaremos especificar explicitamente que o provedor de mapa do site Northwind deve ser usado. Veremos como fazer isso na Etapa 8.

Etapa 8: Exibindo informações do mapa do site usando o provedor de mapa de site personalizado

Com o provedor de mapa de site personalizado criado e registrado no Web.config, estamos prontos para adicionar controles de navegação às Default.aspxpáginas , ProductsByCategory.aspxe ProductDetails.aspx na SiteMapProvider pasta . Comece abrindo a Default.aspx página e arraste um SiteMapPath da Caixa de Ferramentas para o Designer. O controle SiteMapPath está localizado na seção Navegação da Caixa de Ferramentas.

Adicionar um SiteMapPath a Default.aspx

Figura 16: Adicionar um SiteMapPath a Default.aspx (Clique para exibir a imagem em tamanho real)

O controle SiteMapPath exibe uma trilha, indicando o local da página atual no mapa do site. Adicionamos um SiteMapPath à parte superior da página master no tutorial Páginas Mestras e Navegação no Site.

Reserve um momento para exibir esta página por meio de um navegador. O SiteMapPath adicionado na Figura 16 usa o provedor de mapa de site padrão, extraindo seus dados de Web.sitemap. Portanto, a trilha mostra Página Inicial > Personalizando o Mapa do Site, assim como a trilha no canto superior direito.

A trilha usa o provedor de mapa de site padrão

Figura 17: A trilha usa o provedor de mapa de site padrão (clique para exibir a imagem em tamanho real)

Para que o SiteMapPath seja adicionado na Figura 16, use o provedor de mapa de site personalizado que criamos na Etapa 6, defina sua SiteMapProvider propriedade como Northwind, o nome que atribuimos ao NorthwindSiteMapProvider em Web.config. Infelizmente, o Designer continua a usar o provedor de mapa de site padrão, mas se você visitar a página por meio de um navegador depois de fazer essa alteração de propriedade, verá que a trilha agora usa o provedor de mapa de site personalizado.

Captura de tela mostrando como a trilha exibe o provedor de mapa de site personalizado.

Figura 18: A trilha agora usa o provedor NorthwindSiteMapProvider de mapa de site personalizado (clique para exibir a imagem em tamanho real)

O controle SiteMapPath exibe uma interface do usuário mais funcional nas ProductsByCategory.aspx páginas e ProductDetails.aspx . Adicione um SiteMapPath a essas páginas, definindo a SiteMapProvider propriedade em ambos como Northwind. Clique Default.aspx no link Exibir Produtos para Bebidas e, em seguida, no link Exibir Detalhes do Chá Chai. Como mostra a Figura 19, a trilha inclui a seção atual do mapa do site ( Chai Tea ) e seus ancestrais: Bebidas e Todas as Categorias .

Captura de tela mostrando como a trilha exibe a seção atual do mapa do site (Chai Tea) e seus ancestrais (Bebidas e Todas as Categorias).

Figura 19: A trilha agora usa o provedor NorthwindSiteMapProvider de mapa de site personalizado (clique para exibir a imagem em tamanho real)

Outros elementos da interface do usuário de navegação podem ser usados além do SiteMapPath, como os controles Menu e TreeView. As Default.aspxpáginas , ProductsByCategory.aspxe ProductDetails.aspx no download deste tutorial, por exemplo, incluem controles de menu (consulte a Figura 20). Consulte ASP.NET recursos sofisticados de navegação de site do ASP.NET 2.0 e a seção Usando controles de navegação de site dos Inícios Rápidos do ASP.NET 2.0 para obter uma visão mais detalhada dos controles de navegação e do sistema de mapa do site no ASP.NET 2.0.

O controle de menu Listas cada uma das categorias e produtos

Figura 20: o controle de menu Listas cada uma das categorias e produtos (clique para exibir a imagem em tamanho real)

Conforme mencionado anteriormente neste tutorial, a estrutura do mapa do site pode ser acessada programaticamente por meio da SiteMap classe . O código a seguir retorna a raiz SiteMapNode do provedor padrão:

SiteMapNode root = SiteMap.RootNode;

Como o AspNetXmlSiteMapProvider é o provedor padrão para nosso aplicativo, o código acima retornaria o nó raiz definido em Web.sitemap. Para fazer referência a um provedor de mapa de site diferente do padrão, use a SiteMap propriedade da classe s Providers da seguinte forma:

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

Onde name é o nome do provedor de mapa de site personalizado ( Northwind, para nosso aplicativo Web).

Para acessar um membro específico a um provedor de mapa de site, use SiteMap.Providers["name"] para recuperar a instância do provedor e, em seguida, convertê-la para o tipo apropriado. Por exemplo, para exibir a NorthwindSiteMapProvider propriedade s CachedDate em uma página ASP.NET, use o seguinte código:

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

Observação

Certifique-se de testar o recurso de dependência do cache SQL. Depois de visitar as Default.aspxpáginas , ProductsByCategory.aspxe ProductDetails.aspx , vá para um dos tutoriais na seção Edição, Inserção e Exclusão e edite o nome de uma categoria ou produto. Em seguida, retorne a uma das páginas na SiteMapProvider pasta . Supondo que tenha passado tempo suficiente para que o mecanismo de sondagem observe a alteração no banco de dados subjacente, o mapa do site deve ser atualizado para mostrar o novo produto ou nome da categoria.

Resumo

ASP.NET recursos de mapa do site 2.0 incluem uma SiteMap classe, vários controles web de navegação internos e um provedor de mapa de site padrão que espera que as informações do mapa do site persistam em um arquivo XML. Para usar informações de mapa do site de alguma outra fonte, como de um banco de dados, a arquitetura do aplicativo ou um serviço Web remoto, precisamos criar um provedor de mapa de site personalizado. Isso envolve a criação de uma classe que deriva, direta ou indiretamente, da SiteMapProvider classe .

Neste tutorial, vimos como criar um provedor de mapa de site personalizado que baseou o mapa do site nas informações de produto e categoria extraídas da arquitetura do aplicativo. Nosso provedor estendeu a classe e envolveu a StaticSiteMapProvider criação de um BuildSiteMap método que recuperou os dados, construiu a hierarquia do mapa do site e armazenou em cache a estrutura resultante em uma variável de nível de classe. Usamos uma dependência de cache SQL com uma função de retorno de chamada para invalidar a estrutura armazenada em cache quando os Categories dados subjacentes ou Products são modificados.

Programação feliz!

Leitura Adicional

Para obter mais informações sobre os tópicos discutidos neste tutorial, consulte os seguintes recursos:

Sobre o autor

Scott Mitchell, autor de sete livros do ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Ele pode ser contatado em mitchell@4GuysFromRolla.com. ou através de seu blog, que pode ser encontrado em http://ScottOnWriting.NET.

Agradecimentos Especiais

Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores deste tutorial foram Dave Gardner, Zack Jones, Teresa Murphy e Bernadesa Leigh. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, deixe-me uma linha em mitchell@4GuysFromRolla.com.