posts - 115, comments - 1535, trackbacks - 85, articles - 9
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

Question

上次的《十分钟内学会:存储在二维表的树结构如何进行指定深度节点的查询》提到用树结构表示无限分级的商品分类或论坛板块,也就是说这种树结构常常用于存储与导航相关的信息,该如何让网站导航自动按照数据库中存储的树结构自动生成呢?

Answer

继承自SiteMapProvider

要实现站点导航,在ASP.NET 2.0中最方便的就是SiteMap功能了。如果仅仅使用XmlSiteMapProvider则只能从静态的sitemap文件中影射出导航来,无法反映数据库中存储的导航结构。如果要实现根据数据库生成站点导航,就要开发自己的SiteMapProvider。

SiteMapProvider有4个方法要重写,分别是FindSiteMapNode(根据URL获取节点)、GetChildNodes(获取所有子节点)、GetParentNode(获取父节点)、GetRootNodeCore(获取本SiteMapProvider管理范围内的根节点)。对于SiteMap的使用方来说,例如SiteMapDataSource,通过上述4个方法总能在有限步骤内完成SiteMap相关的查询,例如展开若干层SiteMap,以及获取根SiteMapNode到特定SiteMapNode的路径。所以你只要确保自己的SiteMapProvider类正确实现上述4个方法,就可以用于任何SiteMap查询。

SiteMapProvider的灵活性是非常高的,特能构造严格的树,甚至是有向图,例如一个节点只能有一个父节点,但它却可以是多个节点的子节点。这看上去不那么容易理解,但你确实可以这样做,因为SiteMapProvider可以设计为在多个不同的节点输入到GetChildNodes方法时返回的列表都包含同一个特定的节点。如果你觉得你不需要这种灵活性,而需要使用严格的树,并且树是相对静态的(也就是一次构造树就可以用于多次查询),那就应该考虑继承自StaticSiteMapProvider而不是SiteMapProvider。

继承自StaticSiteMapProvider

实现StaticSiteMapProvider的方式与实现SiteMapProvider的方式是不同的。如果你选择了继承自StaticSiteMapProvider,你就相当于确定了导航模型是严格的树。你只需要负责从持久数据中将树结构描述出来,而StaticSiteMapProvider的基础功能会帮你维护树结构在内存中的副本,并由此而提供上述4个要重写的方法中的3个,只剩下GetRootNodeCore需要由你自己重写。

继承自StaticSiteMapProvider的话,除了GetRootNodeCore需要重写以外,还需要重写BuildSiteMap方法,这个正是StaticSiteMapProvider构建内存中树结构的地方。构建操作所需要的方法也由StaticSiteMapProvider提供了,分别是AddNode和RemoveNode方法,另外还有一个Clear方法可以清空内存中的整个树结构。

结合数据库

一般通过数据库构造SiteMap,需要重写StaticSiteMapProvider的Initialize、GetRootNodeCore和BuildSiteMap方法。

Initialize方法继承自ProviderBase,这是一个所有Provider类的基类。在Initialize方法中你能接收到此Provider的名称与配置信息。你可以将ConnectionString写在此Provider在web.config中的配置节,在Initialize时这些配置键值就会传入,你可以在此时将传入的ConnectionString保存到Provider的私有变量中,但不要在Initialize中构造树,因为它仅仅会被调用一次。

GetRootNodeCore用于返回此SiteMapProvider责任范围内的根节点。因为整个SiteMap可以由多个SiteMapProvider提供的SiteMap构成,在跨越SiteMapProvider责任范围边界时,范围内的根节点就关键的标记。通常的做法是,在实现自己的StaticSiteMapProvider时使用一个私有变量保存代表根节点的那个SiteMapNode,而这个SiteMapNode由BuildSiteMap负责构建。

BuildSiteMap是StaticSiteMapProvider派生类的核心,在这里你需要调用Clear方法清空原来的树,然后查询数据库并利用AddNode和RemoveNode方法构建新的树。需要注意的是,此方法必须是线程安全的,因为可能多个客户端访问页面而导致同时向你的StaticSiteMapProvider查询数据,于是多个线程同时调用BuildSiteMap方法。所以重写的BuildSiteMap方法中,通常一开头进行Clear之后就是lock(this),然后再开始构建树。

注意事项

SiteMapProvider和StaticSiteMapProvider的其他方法你喜欢的话也可以去重写,记得要确保线程安全就是了。StaticSiteMapProvider自带的方法都是线程安全的,如果直接调用的话则可依赖其内部的lock而无需自己lock。

如果你的SiteMapProvider要作为XmlSiteMapProvider的下一级SiteMapProvider,则通过在静态sitemap文件中声明<siteMapNode provider="SomeSiteMapProvider" />来配置。如果你的SiteMapProvider中还要引用另外一个SiteMapProvider,那么父SiteMapProvider仅需要在自己提供的SiteMap中包含下一级SiteMap的根节点,这通常可以通过下一级的SiteMapProvider.GetRootNodeCore方法获取,同时设置子SiteMapProvider的ParentProvider属性为父SiteMapProvider,这就行了。

最后,如果你觉得本系列的文章对你有价值,可以考虑订阅Cat in dotNET,或者通过文章下方的书签服务按钮将本文推荐给更多人看。

Feedback

#1楼    回复  引用  查看    

2006-12-29 23:08 by JesseZhao      
正好要用,实验以下

#2楼    回复  引用  查看    

2006-12-29 23:26 by 随心所欲      
不错。以前也使用过SiteMap。在小型站点上使用很划算。

#3楼    回复  引用  查看    

2006-12-31 08:32 by 高海东      
大型站点怎么用那 用户点击是顺序不确定的

#4楼 [楼主]   回复  引用  查看    

2006-12-31 09:19 by Cat Chen      
@高海东
这个看站点的导航是否为树,如果是严格的树则可以用StaticSiteMapProvider,不管站点有多大。SiteMap只是用来表述站点导航,至于在特定的网站怎么用才算好是说不准的,小型站点的话SiteMapDataSource搭配Menu/TreeView/SiteMapPath就够用,大型站点的话这种完全展开SiteMap的方式就不一定适合。

如何让SiteMap适用于大型站点,不仅仅是一个实现方式上的探索,还是一个用户体验方面的探索。但SiteMapProvider这种设计模式应该是适用的。

#5楼    回复  引用  查看    

2006-12-31 09:45 by 随心所欲      
@高海东
可以尝试使用多个SiteMap。甚至在每一个分类里面都有几个SIteMap。

#6楼    回复  引用  查看    

2007-01-03 22:32 by GoGoSonny      
有没有Demo?

#7楼 [楼主]   回复  引用  查看    

2007-01-03 23:19 by Cat Chen      
@GoGoSonny
暂时没有。我曾经做过一个很小规模的试验,代码没有什么通用性,而且现在也不知道那个项目的代码扔去哪里了。

#8楼    回复  引用    

2007-01-05 16:27 by 虫子[匿名] [未注册用户]
不错。

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-12-29 22:30 编辑过


相关链接: