• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    在ASP.NET 2.0中操作数据之六十:创建一个自定义的Database-Driven Site Map Provider

    导言:

      ASP.NET 2.0的网站地图(site map)功能允许页面开发者在一些持久介质(persistent medium),比如一个XML文件里,自己定义一个web程序的site map.一旦定义了之后,我们可以通过System.Web命名空间的SiteMap class类或某个Web导航控件,比如SiteMapPath, Menu, 或TreeView来对其进行访问。site map系统使用的是provider model模式,所以可以创建不同的site map,并将其应用到一个web应用程序。ASP.NET 2.0默认的site map provider,其结构为一个XML文件。在教程《Master Pages and Site Navigation》里我们创建了一个Web.sitemap文件,它就包含了这种结构,并且在教程的每一个新部分里我们都要更新其XML.

      当site map的结构是静态的时候,默认的这种基于XML(XML-based)的site map provider工作正常,就像本系列教程一样。但是在很多时候我们需要动态的site map.如图1的site map,每个种类以及属于该种类的产品在网站的结构里做层次状体系分布。在该site map里,当访问根目录的web页面时,将列出所有的种类;再访问某个具体的种类的根目录时,将列出属于该种类的所有产品;再访问某个具体的产品时将列出该产品的详细信息。


    图1:Categories 和 Products构成了Site Map的层次结构

      这种基于category 和product的结构可以通过"硬编码"的方式添加到Web.sitemap文件.每当对category 或 product进行添加、删除、重命名等操作时,都需要对该文件进行更新。很自然的,如果其结构是通过数据库,或更理想地,是从业务逻辑层来获取的,那么对site map的维护是很简单的。那样的话,只要对products 和 categories进行添加、删除、重命名时,site map会自动的更新以反应这些变化。

      由于ASP.NET 2.0的site map是建立在provider模式的基础上的,因此我们可以创建一个自定义的site map provider,从数据库或某个层来获取数据.在本文,我们创建的provider将从业务逻辑层获取数据。让我们开始吧!

      注意:本文创建的用户定制site map provider仅仅依赖于系统的层及其数据模式(data model)。Jeff Prosise的文章《Storing Site Maps in SQL Server》(http://msdn.microsoft.com/msdnmag/issues/05/06/WickedCode/)
    和《The SQL Site Map Provider You've Been Waiting For》
    (http://msdn.microsoft.com/msdnmag/issues/06/02/wickedcode/default.aspx)
    考察了将site map数据存储在SQL Server的方法。

    第一步:创建用户定制Site Map Provider页面

    在创建用户定制Site Map Provider之前,先添加本章将用到的ASP.NET页面。首先添加一个名为SiteMapProvider的文件夹;然后在文件夹里添加如下所示的页面确保采用母版Site.master:

    Default.aspx
    ProductsByCategory.aspx
    ProductDetails.aspx

    同样,在App_Code文件夹里添加CustomProviders


    图2:添加相关的ASP.NET页面.

    由于这部分只有一篇文章,没有必要使Default.aspx页面列出本部分的文章;我们将在Default.aspx里用一个GridView控件来列出categories,在第二步里探讨.

    然后,更新Web.sitemap使其包含对Default.aspx页面的引用。特别的,在“Caching” 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:Site Map现在包含了本章的条目

      本教程主要考察如何创建一个用户自定义的site map provider,及对设置web应用程序进行包含该site map provider.具体来讲,它返回的网站地图(site map)不仅包含了根节点,而且包含每个种类节点和产品节点,就像图1显示的那样。总的来说,网站地图里的每一个节点都对应一个具体的URL.就我们的网站地图而言,根节点的URL为~/SiteMapProvider/Default.aspx,它列出了所有产品和种类;每个种类节点对应的URL 为~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID,它根据指定的categoryID列出该种类的所有产品;最后,每个产品对应的URL为~/SiteMapProvider/ProductDetails.aspx?ProductID=productID, 它根据指定的productID值,列出该产品的详细信息。

      首先,让我们创建Default.aspx, ProductsByCategory.aspx和ProductDetails.aspx页面。我们将分别在第二、三、四步创建这些页面.因为本文的重点是site map providers,并且这种主/从页面在前面的教程里已经讨论过了,我们在第2到第4步将一笔带过,如果你对这种主/从页面页面不是很了解的话,请参考前面的教程之9《Master/Detail Filtering Across Two Pages》.

    第二步:将Categories显示出来

      打开文件夹SiteMapProvider里的Default.aspx页面,在设计模式里从工具箱拖一个 GridView控件到页面,设置其ID为Categories.从其智能标签里,将其绑定到一个名为CategoriesDataSource的ObjectDataSource,设置其使用CategoriesBLL类的 GetCategories方法。因为该GridView控件只是显示categories而不修改数据,因此在UPDATE, INSERT和 DELETE标签里选“(None)”.


    图4:设置ObjectDataSource使用GetCategories方法返回Categories


    图5:在UPDATE, INSERT和DELETE标签里选“(None)”

      设置完成后,Visual Studio会自动的添加CategoryID, CategoryName, Description, NumberOfProducts 和 BrochurePath这些绑定列(BoundField),修改GridView,使其只包含CategoryName 和 Description两列,且将CategoryName绑定列的HeaderText属性改为“Category”.

      然后,添加一个HyperLinkField,将其放在最左边,设其DataNavigateUrlFields属性为 CategoryID;DataNavigateUrlFormatString 属性为 ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0};再将Text属性设置为“View Products”.


    图6:为GridView添加一个HyperLinkField

    创建完ObjectDataSource并定制GridView控件的列后,这2个控件的声明代码看起来应该和下面的差不多:

    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页面,点某个类的“View Products”链接,将会转到ProductsByCategory.aspx?CategoryID=categoryID页面,该页面我们将在第三步新建。


    图7:每个类都有一个“View Products”链接

    第三步:显示指定类的所有产品

      打开ProductsByCategory.aspx页面并添加一个GridView控件,设其ID为ProductsByCategory.从其智能标签,将其绑定到一个名为ProductsByCategoryDataSource的ObjectDataSource;设置它使用ProductsBLL类的 GetProductsByCategoryID(categoryID)方法;在UPDATE, INSERT,和 DELETE标签里选择“(None)”.


    图8:使用ProductsBLL类的GetProductsByCategoryID(categoryID)方法

      设置向导的最后一步是指定categoryID的参数来源,因为此信息是通过查询字符串(querystring field)CategoryID来传递的,因此在参数来源里选QueryString,在QueryStringField里输入“CategoryID”;如图9所示,点Finish完成设置.


    图9:为参数categoryID指定CategoryID Querystring Field

      完成设置后,Visual Studio将为GridView添加相应的绑定列以及CheckBo列;将除ProductName, UnitPrice, SupplierName外的列删除掉。将这3个列的HeaderText属性分别设置为“Product”, “Price”, and “Supplier”, 将UnitPrice列格式化为货币形式.

      然后,添加一个HyperLinkField列,并将其放在最左边;设其Text属性为“View Details”,设其DataNavigateUrlFields属性为ProductID;其DataNavigateUrlFormatString属性为 ~/SiteMapProvider/ProductDetails.aspx?ProductID={0}.


    图10:添加一个“View Details” 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页面,点Beverages(饮料)的“View Products”链接,这将转到ProductsByCategory.aspx?CategoryID=1页面,显示饮料类的所有产品的names, prices, 和 suppliers信息(见图11)。尽管改进该页面吧,添加一个链接以方便用户返回上一页(Default.aspx) .还可以添加一个DetailsView 或 FormView控件来显示该种类的名称和描述。


    图11:显示Beverages类的Names, Prices, Suppliers信息

    第四步:显示产品的详细信息

      最后要创建的页面—ProductDetails.aspx,是用来显示指定产品的详细信息的。打开ProductDetails.aspx页面,从工具箱拖一个DetailsView控件到页面,设置其ID为ProductInfo,并清除其Height 和 Width属性值。在其智能标签里,绑定到一个名为ProductDataSource的ObjectDataSource,设置该ObjectDataSource使用ProductsBLL类的GetProductByProductID(productID)方法。在UPDATE, INSERT,和DELETE标签里选“(None)”.


    图12:设置该ObjectDataSource控件调用GetProductByProductID(productID)方法

      最后,需要设置参数productID的来源,由于数据通过查询字符串ProductID来传递,在参数源下拉列表里选QueryString,在QueryStringField对话框里输入“ProductID”. 最后,点Finish按钮完成设置。


    图13:设置参数productID来源于查询字符串

      完成设置后,Visual Studio会为DetailsView控件添加相应的绑定列和CheckBox列,移除ProductID, SupplierID, 和CategoryID列,剩下的列想怎样设就怎样设置吧。我对界面做了些优化,这样的话,声明代码看起来像下面这样:

    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页面,点种类Beverages的“View Products”链接;再点产品Chai Tea的“View Details”链接。这将转到ProductDetails.aspx?ProductID=1页面,其显示的是Chai Tea的详细信息(如图14所示).


    图14:Chai Tea的Supplier, Category, Price等信息显示出来了

    第五步:理解Site Map Provider的内部处理机制

      site map呈现的是源于某种层次结构的SiteMapNode实例集(a  collection of SiteMapNode instances)。其必须有一个根节点,所有的非根节点都有一个父节点,且每个节点都可以有任意数量的子节点.每个SiteMapNode对象对应的是website体系结构的某个部分。这些部分通常都有对应的web页面,因此,SiteMapNode class类有像Title, Url, 和 Description这样的属性,它们用来描述SiteMapNode所对应部分的相关信息。
    还有一个Key属性用来专门唯一的标识这些SiteMapNode;除此以外,还有ChildNodes, ParentNode, NextSibling, PreviousSibling等等.


    图15显示的是对应于图1的site map的总体结构,只是更细化了而已.

       可以通过命名空间System.Web的SiteMap class类来访问site map;该类的RootNode属性返回网站地图的根目录的SiteMapNode实例;CurrentNode属性返回的是这种SiteMapNode,其Url属性刚好与当前请求页面的URL匹配.ASP.NET 2.0的Web导航控件的内部就会用到SiteMap class类.

      当访问SiteMap class类的属性时,必须将网站地图的层次结构从某个介质传入内存(memory).SiteMap class类并不是通过“硬编码”的方式来处理网站地图的逻辑关系,而是通过某种site map provider来工作.在默认情况下,使用的是XmlSiteMapProvider class类,它从一个标准的XML文件读取网站地图的结构.不过,我们稍微做点工作就可以创建自己的site map provider.

      所有的site map providers都继承自SiteMapProvider class类,该类包含了site map providers要用到的最基本的方法和属性,不过略去了很多执行细节.site map providers要用到的第二个类是StaticSiteMapProvider class类,它对SiteMapProvider class类进行了扩充,包含了更多的必要的函数.在其内部,StaticSiteMapProvider将网站地图的SiteMapNode实例存储在一个哈希表(Hashtable)里,并包含了AddNode(child, parent), RemoveNode(siteMapNode), Clear()等方法,以对哈希表里的SiteMapNodes执行添加、删除等操作.另外,XmlSiteMapProvider也继承自StaticSiteMapProvider.

      当创建自定义的site map provider时,要对StaticSiteMapProvider进行扩充,重写(overrid)2个抽象方法——BuildSiteMap 和 GetRootNodeCore. 对BuildSiteMap而言,就像它的名字暗示的那样,将网站地图的结构从某种介质里按层次结构装载进内存;而GetRootNodeCore返回的是网站地图的根目录.

      在使用某个site map provider时,需要在应用程序的配置文件里进行注册(registered)
    .默认情况下,XmlSiteMapProvider class类被注册为AspNetXmlSiteMapProvider.为对额外添加的site map providers进行注册,可以在Web.config文件里添加如下的代码:

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

      name属性可以为site map provider指派一个易读的名称;type属性决定了该site map provider的类型.当创建完我们定制的site map provider后,我们将在第七步为name 和type属性赋值.

      当第一次从SiteMap class类访问site map provider时,site map provider class类都应该被实例化,并在web应用程序的整个生命周期里都驻留在内存.

      基于性能等方面的考虑,我们应该将对驻留在内存里的网站地图结构进行数据缓存,每次调用BuildSiteMap的方法时,直接返回缓存的数据而不用重新检索数据.在任何情况下,如果我们不对BuildSiteMap对应的网站结构进行缓存的话,每次调用时,我们都需要通过“层”来重新检索产品和种类的信息(这将最终导致对数据库的查询).我们在前面的缓存章节探讨过缓存数据“过时”的问题,为此,我们要么使用基于时间,要么使用基于SQL cache dependency的缓存技术.

      注意:一个site map provider可以任意地重写(override)Initialize method方法.Initialize 方法是当site map provider第一次实例化的时候被调用的,并可以将我们在Web.config 文件的add>元素里赋值的用户自定义属性值传递给它,比如:add name="name" type="type" customAttribute="value" />.当一个页面开发者希望指定各种与site map provider相关的设置,而又不希望修改site map provider的代码的时候,这样做很有用.比如,假如我们希望不通过“层”而直接从数据库读取category 和 products的数据时,我们当然希望页面开发者调用Web.config文件里的数据库连接字符串,而不使用site map provider代码里的“硬编码”值.我们不打算在第六步创建的自定义site map provider里重写Initialize方法.见Jeff Prosise的文章《Storing Site Maps in SQL Server》(http://msdn.microsoft.com/msdnmag/issues/05/06/WickedCode/)

    第六步:创建自定义的Site Map Provider

      要想创建一个自定义的site map provider来构建源于Northwind数据库里的categories 和 products信息的网站地图(site map),我们需要创建一个类来扩展StaticSiteMapProvider.在前面我们在App_Code文件夹里添加了一个CustomProviders文件夹,在该文件夹里添加名为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 statement声明。lock statement每次只允许“单线程操作”(one thread at a time to enter),以避免“多线程操作”之间的冲突.

      属于“类级别”(class-level)的SiteMapNode变量—root,用来缓存网站地图结构.当网站地图第一次被“结构化”,或“源数据”发生变动后的第一次“结构化”时,root为null值,在“结构化”的过程中,root被赋值为网站地图的根节点;所以,当第二次调用BuildSiteMap方法时,root就不为null值了.自然,只要root不为null,直接将网站地图结构返回,而用不着重新创建.

      如果root为null,那么将根据product 和 category信息创建网站地图结构.为此,先要创建一个SiteMapNode实例,再调用StaticSiteMapProvider class类的AddNode method方法来构建网站地图的层次体系,再将SiteMapNode实例存储进一个哈希表.在我们构建层次体系之前,我们首先调用Clear method方法,将内部的哈希表清空;然后,调用ProductsBLL class类的GetProducts()方法,把返回的ProductsDataTable存储进局部变量.

      创建网站地图结构从创建根节点并赋值给root开始,本章要用到的SiteMapNode's constructor重载,接受如下的信息:

    对一个site map provider (this)的引用.

    SiteMapNode的Key值:对每个SiteMapNode而言,这个待定值必须是唯一的.

    SiteMapNode的Url值:Url为可选项,但一旦指定的话,每个SiteMapNode的Url值必须是唯一的.

    SiteMapNode的Title值:此为必选项.

      AddNode(root) method方法将SiteMapNode root添加给网站地图作为根节点。然后,遍历ProductsDataTable里的所有ProductRow,如果当前product的category所对应的SiteMapNode已经存在的话,那么引用该SiteMapNode;如果不存在的话,则为该category创建一个新的SiteMapNode,并且调用AddNode(categoryNode, root) method方法,将其作为SiteMapNode root的子节点进行添加.当找到或创建category对应的SiteMapNode后,创建一个当前product对应的SiteMapNode,并通过AddNode(productNode, categoryNode)方法将其作为category SiteMapNode的子节点进行添加.注意,category SiteMapNode的Url属性为~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID;而product SiteMapNode的Url属性为~/SiteMapNode/ProductDetails.aspx?ProductID=productID.

      注意:对那种CategoryID为NULL值的产品,统统将其归为一个category,其对应的category SiteMapNode的Title属性可设置为“None”;Url属性设置为空字符串。我将其Url设置为空字符串是因为ProductBLL class类的GetProductsByCategory(categoryID)方法无法返回那些CategoryID值为NULL的产品.不过我鼓励你对本教程进行扩展,使该category SiteMapNode的Url属性对应一个ProductsByCategory.aspx页面,该页面专门用来展示那些CategoryID为NULL的产品.

      当完成site map的构建后,将一个AggregateCacheDependency object对象添加到data cache,该对象使用基于Categories 和 Products表的SQL cache dependency技术。我们在前面的教程里探讨过SQL cache dependencies,不过我们自定义的site map provider使用的是重载(overload)的data cache的Insert方法,该重载方法接受一个delegate作为输入参数.具体而言,我们将传入一个CacheItemRemovedCallback delegate,其指向OnSiteMapChanged method方法,该方法定义在NorthwindSiteMapProvider class类里
    注意:内存里的site map表述是缓存在一个“类级”(class-level)变量root里的.由于只有一个site map provider的实例(instance),并且对web应用程序的线程来说都是共享的,这个类级变量当作缓存服务。BuildSiteMap method方法也会用到data cache,但仅仅做作为一种探测Categories 或 Products表里的数据发生改变的方法。注意添加到data cache里的仅仅是当前的date和time,实际的site map数据并没有添加到data cache.

    BuildSiteMap method方法最后返回网站地图的根节点.

      剩下的方法就比较简单易懂了.GetRootNodeCore方法用来返回根节点,由于BuildSiteMap返回根节点root, GetRootNodeCore方法仅仅返回BuildSiteMap方法的返回值.当缓存条码被清除掉时,OnSiteMapChanged方法将root设置为null;当root为null的时候,当下一次调用BuildSiteMap时,将重新创建地图网站结构.最后,如果data cache里存储有date 和 time值的话,CachedDate属性将返回这些值.页面开发员可以用该属性来探测site map数据最近被缓存的时间.

    第七步:对NorthwindSiteMapProvider进行登记

      为了使用我们在第六步创建的NorthwindSiteMapProvider site map provider,我们需要在Web.config文件的siteMap>部分进行注册.具体来说,将下面的代码添加到Web.config文件的system.web>部分:

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

      上述代码阐明了如下2个事实:第一,它指明了“内置”的AspNetXmlSiteMapProvider为默认的site map provider;第二,它将我们在第六步创建的用户自定义site map provider进行了注册,取名为“Northwind”.
    注意:对那些位于在App_Code文件夹的site map providers而言,type属性的值就是类的名称.还一种方法,我们可以用一个单独的类库工程来创建自定义的site map provider,将其编译文件放置在/Bin目录;如果是那样的话,type属性就变成了“Namespace.ClassName, AssemblyName”.

      更新Web.config文件后,花点时间在浏览器里登录本教程的任何一个页面,我们注意到左边的导航界面跟以前一样,那是因为我们把AspNetXmlSiteMapProvider作为默认的provider,为了使导航用户界面使用我们定制的NorthwindSiteMapProvider,我们应明确的指定使用“Northwind” site map provider,我们将在第八步完成.

    第八步:使用定制的Site Map Provider来显示网站地图信息

      把我们定制的site map provider注册到Web.config文件后,我们可以将导航控件添加到SiteMapProvider文件夹里的Default.aspx, ProductsByCategory.aspx, 和ProductDetails.aspx页面.首先,打开Default.aspx页面进入设计模式,从工具箱拖一个SiteMapPath控件到页面。该控件位于工具箱的导航区域.


    图16:为Default.aspx页面添加一个SiteMapPath控件

      SiteMapPath控件包含一个breadcrumb,用来显示当前页面在网站地图里的位置。我们在第三章《模板页和站点导航》里在模板页的顶部添加了一个SiteMapPath控件.

      花点时间在浏览器里登录页面,我们在图16里添加的SiteMapPath控件使用的是默认的site map provider,它从Web.sitemap文件获取数据,因此breadcrumb显示为“Home > Customizing the Site Map”.如下图:


    图17:Breadcrumb使用的是默认的Site Map Provider

      要使在图16里添加的SiteMapPath使用我们定制的site map provider的话,设其SiteMapProvider property属性为“Northwind”, 这个名字是我们在Web.config文件里分配给NorthwindSiteMapProvider的.不过,在设计器里依然使用的是默认的site map provider,但是如你在浏览器里登录该页面的话,你将看到breadcrumb使用的是我们定制的site map provider了.


    图18:Breadcrumb现在使用的是我们定制的NorthwindSiteMapProvider

      SiteMapPath控件将在ProductsByCategory.aspx 和 ProductDetails.aspx页面展示更具功能性的用户界面.在这2个页面里添加SiteMapPath控件,设置其SiteMapProvider属性为“Northwind”. 在Default.aspx页面里点击Beverages类的“View Products”链接,然后再点Chai Tea的“View Details”链接,如图19所示,breadcrumb显示的是当前的网站地图节点(“Chai Tea”),及其上级节点:“Beverages” 和“All Categories”.


    图19:Breadcrumb现在使用的是我们定制的NorthwindSiteMapProvider

      除了SiteMapPath外,还可以使用其它的导航控件,比如Menu 和 TreeView控件.本章的下载代码里,Default.aspx, ProductsByCategory.aspx,和ProductDetails.aspx页面都包含Menu控件(见图20).要想更深入的了解ASP.NET 2.0里的导航控件和site map体系的话,可参阅《ASP.NET 2.0 QuickStarts》系列(http://quickstarts.asp.net/QuickStartv20/aspnet/)的《Examining ASP.NET 2.0's Site Navigation Features》和《Using Site Navigation Controls》部分.


    图20:Menu控件列出了所有的Categories 和 Products

    就像在本教程前面提到的那样,网站地图结构可以通过SiteMap class类来进行访问,下面的代码返回默认的provider的root SiteMapNode:

    SiteMapNode root = SiteMap.RootNode;

    由于AspNetXmlSiteMapProvider是默认的provider,上述代码返回的是定义在Web.sitemap文件里的根节点,要引用其它的site map provider的话,使用SiteMap class类的Providers property属性,如:

    SiteMapNode root = SiteMap.Providers["name"].RootNode;
    这里的name是用户定制的site map provider的名称(就本文而言,为“Northwind”)

    要访问某个具体的site map provider,使用SiteMap.Providers["name"]来获取该provider的实例,再将其转换成恰当的类型。比如,要展示NorthwindSiteMapProvider的CachedDate property属性,使用如下的代码:

    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 cache dependency属性,访问完Default.aspx, ProductsByCategory.aspx, 和 ProductDetails.aspx页面后,转到本系列教程的《编辑插入和删除数据》部分的任一个页面,编辑某个category 或 product的名称;然后再转到SiteMapProvider文件夹里的某个页面,假设时间足够长,长到检测机制(polling mechanism)发现“源数据库”已经发生了改动,那么site map应该被更新以显示新的product 或 category名字.

    结语:

      ASP.NET 2.0的site map属性包含一个SiteMap class类,一系列内置的的导航Web控件,以及一个默认的site map provider.为了使用来自某些数据源的site map信息——比如数据库、系统的“层”、或者某些Web服务,我们需要创建一个用户定制的 site map provider.这就要创建一个类,该类直接或间接的源自SiteMapProvider class类.

      本章我们探讨了如何创建一个用户定制的site map provider,它以一个由product 和 category信息构成的site map为基础.我们的provider对StaticSiteMapProvider class类进行了扩充,并创建了一个BuildSiteMap method方法来获取数据、构建site map的层次体系,并且将最终的网站地图结构缓存在一个“类级”的变量里.我们使用一个SQL cache dependency来确保当Categories 或 Products的“源数据”发生改动时使缓存的数据失效.

      祝编程快乐!

    作者简介

      本系列教程作者 Scott Mitchell,著有六本ASP/ASP.NET方面的书,是4GuysFromRolla.com的创始人,自1998年以来一直应用 微软Web技术。大家可以点击查看全部教程《[翻译]Scott Mitchell 的ASP.NET 2.0数据教程》,希望对大家的学习ASP.NET有所帮助。

    您可能感兴趣的文章:
    • 在ASP.NET 2.0中操作数据之五十九:使用SQL缓存依赖项SqlCacheDependency
    • 在ASP.NET 2.0中操作数据之六十一:在事务里对数据库修改进行封装
    • 在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
    • 在ASP.NET 2.0中操作数据之六十三:GridView实现批量删除数据
    • 在ASP.NET 2.0中操作数据之六十四:GridView批量添加数据
    • 在ASP.NET 2.0中操作数据之六十五:在TableAdapters中创建新的存储过程
    • 在ASP.NET 2.0中操作数据之六十六:在TableAdapters中使用现有的存储过程
    • 在ASP.NET 2.0中操作数据之六十七:在TableAdapters中使用JOINs
    • 在ASP.NET 2.0中操作数据之六十八:为DataTable添加额外的列
    • 在ASP.NET 2.0中操作数据之六十九:处理Computed Columns列
    上一篇:在ASP.NET 2.0中操作数据之五十九:使用SQL缓存依赖项SqlCacheDependency
    下一篇:在ASP.NET 2.0中操作数据之六十一:在事务里对数据库修改进行封装
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    在ASP.NET 2.0中操作数据之六十:创建一个自定义的Database-Driven Site Map Provider 在,ASP.NET,2.0,中,操作,数据,