当前位置: 移动技术网 > IT编程>开发语言>.net > 在ASP.NET 2.0中操作数据之六十:创建一个自定义的Database-Driven Site Map Provider

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

2017年12月12日  | 移动技术网IT编程  | 我要评论

齿轮设计,曾仕强,网游之异世王者

导言:

  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页面时,将列出所有的种类;再访问某个具体的种类的根目录时,将列出属于该种类的所有产品;再访问某个具体的产品时将列出该产品的详细信息。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908194975.jpg
图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》()
和《the sql site map provider you've been waiting for》
()
考察了将site map数据存储在sql server的方法。

第一步:创建用户定制site map provider页面

在创建用户定制site map provider之前,先添加本章将用到的asp.net页面。首先添加一个名为sitemapprovider的文件夹;然后在文件夹里添加如下所示的页面确保采用母版site.master:

default.aspx
productsbycategory.aspx
productdetails.aspx

同样,在app_code文件夹里添加customproviders

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908198017.jpg
图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的更新后,花点时间在浏览器里登录页面,在左面的菜单里包含了本教程的条目。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908193533.jpg
图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)”.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908197075.jpg
图4:设置objectdatasource使用getcategories方法返回categories

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908206604.jpg
图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”.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908209646.jpg
图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页面,该页面我们将在第三步新建。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908205162.jpg
图7:每个类都有一个“view products”链接

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

  打开productsbycategory.aspx页面并添加一个gridview控件,设其id为productsbycategory.从其智能标签,将其绑定到一个名为productsbycategorydatasource的objectdatasource;设置它使用productsbll类的 getproductsbycategoryid(categoryid)方法;在update, insert,和 delete标签里选择“(none)”.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908201462.jpg
图8:使用productsbll类的getproductsbycategoryid(categoryid)方法

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

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908209990.jpg
图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}.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908215505.jpg
图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控件来显示该种类的名称和描述。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908213562.jpg
图11:显示beverages类的names, prices, suppliers信息

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

  最后要创建的页面—productdetails.aspx,是用来显示指定产品的详细信息的。打开productdetails.aspx页面,从工具箱拖一个detailsview控件到页面,设置其id为productinfo,并清除其height 和 width属性值。在其智能标签里,绑定到一个名为productdatasource的objectdatasource,设置该objectdatasource使用productsbll类的getproductbyproductid(productid)方法。在update, insert,和delete标签里选“(none)”.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908216320.jpg
图12:设置该objectdatasource控件调用getproductbyproductid(productid)方法

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

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908215849.jpg
图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所示).

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908213905.jpg
图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等等.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908218420.jpg
图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》()

第六步:创建自定义的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控件到页面。该控件位于工具箱的导航区域.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908229705.jpg
图16:为default.aspx页面添加一个sitemappath控件

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

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

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908223464.jpg
图17:breadcrumb使用的是默认的site map provider

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

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908224749.jpg
图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”.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908222806.jpg
图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》系列()的《examining asp.net 2.0's site navigation features》和《using site navigation controls》部分.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908224091.jpg
图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有所帮助。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网