在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》
()
考察了将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有所帮助。