可扩展的 DotNetNuke Google Sitemap 生成器






4.14/5 (5投票s)
使用 ASP.NET Provider 模型构建 DotNetNuke 站点地图生成器。
引言
在之前的文章中,我写过一篇关于使用 ASP.NET Provider 模型构建谷歌站点地图生成器的文章。实际上,整个基础是一个学习练习,旨在提高我对 Provider 模型 intricacies 的理解。我以前只接触过现有代码,从未从“文件->新建项目”开始编写过自己的 provider,您懂我意思吧。这篇新文章将这个想法进一步,并将其应用于基于 DotNetNuke 的网站。
这一切都是为“真正”的项目做铺垫,那就是使用 Provider 模型开发一个 DotNetNuke 特定的谷歌站点地图生成器。当然,这种工具已经存在了,我自己也下载并使用了 bitethebullet.co.uk 开发的 DotNetNuke 谷歌站点地图——其背后的人似乎希望保持匿名,但做得很好。然而,我发现它在使用上有些局限,尤其是在添加了模块后,单个页面(选项卡)的 URL 数量开始激增。标准的 DNN Blog 模块就是一个例子——一个页面可以有许多不同的 URL——每个博客和博客条目都有一个。然后,还有基于日期的存档 URL。因此,需要一个更复杂、针对模块的谷歌站点地图工具(并且已经需要了)。
我的需求
我基于 DotNetNuke 开发网站,因此,大多数网站都有一个共同的核心,基本上是相同的——一系列包含 HTML 模块的页面。特定的网站具有特定的功能,例如电子商务模块、咨询模块,当然还有博客模块。所以,要获得一个能够满足所有不同(换句话说:复杂)模块类型的谷歌站点地图生成器,我意识到我需要一个灵活的模型。于是,Provider 模型应运而生。
需求是:
- 基本的 SiteMap 生成器,用于索引“普通”的 DotNetNuke 页面,并遵守关于“隐藏”页面和仅对注册用户可用的页面的规则。
- 可扩展的模型,以便使用 web.config 进行运行时配置,以适应更复杂的模块。
- 所有内容都通过程序集完成,不包含 .aspx、.ashx 或任何其他类型的 ASP.NET 页面。所有内容都放在 \bin 目录中进行部署。
我的设计
以我最初的谷歌站点地图 provider 模型为起点,我添加了 DNN 特定的代码。最初的原型适用于普通的 ASP.NET 网站,它只是迭代服务器上的所有文件,并根据物理文件构建站点地图。由于 DotNetNuke 使用单个页面(default.aspx)并根据请求 URL 确定内容,因此新的站点设计依赖于读取特定门户的 DNN Tabs 集合,并以此方式构建站点地图。
必须检查每个页面的安全级别——不向公众显示的页面不会包含在站点地图中,并且隐藏页面会根据 web.config 文件中设置的开关显示/隐藏在站点地图中。
这工作得相当好,并为任何“标准”DNN 网站(版本 4 及以上)生成了一个成功的站点地图。这满足了我的第一个需求。
与我早期原型设计一样,ASP.NET Handler 被构建到 provider DLL 中,因此 handler 和 provider 在同一个程序集中。这在概念上并不完全正确,但我选择了二进制的简单性而不是真正分离组件。这满足了我的第二个需求。
扩展模型
如我的第二个需求所述,需要一个灵活且可扩展的模型来处理 DotNetNuke 页面上更复杂的模块。这就是 ASP.NET Provider 模型发挥作用的地方。我通过设置类访问修饰符来确保基础 Provider 类是可派生的,并创建了一个名为 BlogGoogleSiteMap 的新程序集。该程序集中的主要类型 BlogGoogleSiteMapProvider
继承自原始的 GoogleSiteMapProvider
类型。这赋予了它生成站点地图、将起始 URL 转换为 DotNetNuke PortalAlias
实例以及其他辅助功能的基础功能。然而,通过重新定义 SitePages(siteURL)
方法(该方法返回给定实际 URL 的逻辑页面 URL 对象集合),新的 BlogGoogleSiteMap
provider 可以处理特定的博客细微差别。
将它们缝合在一起是通过基础 DNN Google Site Map Provider 类型中的一个过程完成的。此方法读取特定 DNN 页面上的每个模块(实际上是 TabInfo
对象的实例)。对于这些模块中的每一个,都会加载 ModuleDef
类型实例,并读取模块定义的 FriendlyName。这为我们提供了模块在页面上的唯一标识符——实际上,它告诉我们页面的内容是什么。您可以在此处实现一个 switch
或 Case
语句,并根据模块定义调用特定的代码块。但这将违反第二个需求——可扩展模型。每个特定的模块定义意味着需要引用一个新的模块特定程序集,并且整个 SiteMap Provider 将失去可移植性。您需要将您编程过的每个模块的所有二进制文件都上传到谷歌站点地图 provider 中。
相反,该设计使用一个简单的命名格式 ModuleDef.FriendlyName + ".GoogleSiteMapProvider"
来在 web.config 中定位特定模块的正确 provider。
例如,当在 DNN 页面上的模块集合中搜索博客条目时,您会遇到模块定义友好名称为“View_Blog”。要为该模块定义谷歌站点地图 provider,只需要在 <googlesitemaps>
web.config 条目中添加一个条目。
<add name="View_Blog.GoogleSiteMapProvider"
type="iFinity.DNN.Modules.GoogleSiteMap.BlogGooogleSiteMapProvider,
iFinity.DNN.BlogGoogleSiteMapProvider" />
基础 DNNGoogleSiteMap Provider 具有此 FriendlyName => 内置的正确 Google Sitemap Provider 代码。页面上的每个模块都会在 <googlesitemaps>
部分中检查是否存在条目。当找到匹配的模块时,它将加载指定的 Provider 并调用它来获取特定页面/模块组合的 URL 列表。如果它在配置部分中找不到模块条目,它将只返回特定页面的“普通”DNN 页面 URL,而不会尝试加载任何模块特定的 Provider。
每个模块一个 Provider
有了这个框架,DNN 中的任何使用标准页面 URL 以外的其他方式来提供内容的特定模块都可以为其开发一个特定的 GoogleSiteMap Provider。然后,该 Provider 可以直接放入使用该模块的特定网站中。因此,如果您正在创建许多不同的 DNN 网站,并且它们都使用了不同的模块组合,您可以通过将不同的程序集放入 \bin 目录并修改 web.config 文件,来快速配置所需的谷歌站点地图。不需要重新编译或修改 DNN 核心。
DotNetNuke 博客谷歌站点地图 Provider
特定的博客谷歌站点地图 Provider 以特定方式工作。首先,通过逆向工程博客代码并进行一些示例测试,我发现每个门户实际上只有一个“博客集合”。您可以将博客和博客条目放置在网站的特定页面上,并且可以将特定博客与特定页面关联起来,但这种关系是 Portal->Blogs->Entries,而不是 Portal->Page->Blogs->Entries,正如您初看时可能预期的那样。实际上并没有办法将特定博客与特定页面关联起来,因为通过博客模块随附的标准链接导航,所有博客都可以在所有博客特定的页面上查看。我理解设计者为何这样设计,因为它为访问者提供了完全的灵活性,可以浏览网站上所有博客/条目,而无需四处寻找。
考虑到这一点,每个页面都可以关联完整的博客相关 URL。根据博客的数量,这可能会迅速增加。但是,我见过的大多数博客安装都倾向于将整个博客集合放在网站的一个页面上,然后就此打住。
Blog Sitemap Provider 遍历每个博客和博客条目,并为每个特定 URL 添加一个条目。特定页面的 URL 集合可能是:
//the standard blog page
http://www.yoursite.com/blog/tabid/15/default.aspx
//the standard page for BlogID = 1
http://www.yoursite.com/blog/tabid/15/blogid/1/default.aspx
//the specific URL for EntryID = 2 (entries are unique across all blogs)
http://www.yoursite.com/blog/tabid/15/entryid/2/default.aspx
//the specific URL for BlogID = 1, EntryID = 2
http://www.yoursite.com/blog/tabid/15/blogid/1/entryid/2/defaut.aspx
最后一个 URL (blogID, entryID) 会产生与列表中第三个 URL (仅 entryID) 相同的页面,因为每个 EntryID 在整个门户中都是唯一的,无论它属于哪个实际的博客。这意味着这两个 URL 提供相同的页面,并且遵循谷歌关于相同内容的指南,最后一个 URL 不会被 Blog Google Sitemap Provider 提交。
Blog Google Sitemap Provider 还在 Provider 条目中有一个可配置的 web.config 条目,用于指定是否包含博客存档。将其设置为 true,它将包含指向博客存档的链接。现在,这可能与单个条目 URL 相同,也可能不同,具体取决于该网站是否习惯于每天只有一个条目。网站所有者是否认为将存档包含在谷歌站点地图中有必要,这是一个判断问题。存档具有特定的 URL 模式——而且不知何故,这总是会回退到参数驱动(非友好?——)的 URL,例如:
http://www.yoursite.com/default.aspx?tabid=15&BlogDate=2006-10-11
实际上,博客页面会显示该日期之前月份的所有条目。因此,提交日期 11-Oct-2006 会返回从 2006 年 10 月 1 日到 2006 年 10 月 11 日的所有博客条目。同样,这是否会产生一个唯一的页面取决于条目的数量。一个月只有一个条目的博客会产生大致相同的页面内容,但每周有一个或两个条目的博客将提供足够多的页面,值得包含存档。请记住,每个博客条目都会有一个不同的 URL。谷歌站点地图文件最多有 10,000 个 URL 的限制,但如果您有 10,000 个博客条目,也许写作生涯在等着您,而不是配置谷歌站点地图。
页面更新频率和页面优先级元素在标准的谷歌 schema 中是可选的,有一种观点认为“不要提供任何信息,只会惹麻烦”。我不同意这一点,显然谷歌想知道您的页面更新频率。有些人可能想说“每天”和“priority = 1”,认为这会让他们获得更频繁的 Googlebot 访问,并某种程度上提高页面排名。谷歌对此非常明确,并在其 Sitemap Help 部分指出,这些只是提示,Googlebot 是否遵循这些提示由它自己决定。我认为没有必要告诉 Googlebot 一个页面每天都在更新,而实际上它从未改变过。一个不太聪明的程序员就可以比较缓存上次与当前页面版本,并确定没有内容不同。
考虑到这一点,在 Blog Provider 中,我开发了一个简单的算法来比较条目之间的时间,并根据页面更新频率提供一个粗略的估计。对于博客页面,这是新条目的数量。对于条目页面,这实际上取决于添加到博客条目的评论数量。如果没有人评论(或您关闭了评论),那么该条目一旦发布,可能永远不会改变。因此,在相关的 Sitemap 中将显示为 PageUpdateFrequency=Never
。
页面优先级是一个相对术语——通过填写它,您正在对您自己网站内的页面按重要性进行排名。考虑到这一点,Blog Provider 将最新的博客页面评为 web.config 中设置的 defaultPagePriority
。但是,它会将博客存档的值减半,因为您会期望存档页面不如新发布的内容重要。我想对 Sitemaps 和 Web 日志进行一项长期研究,以确定更改页面更新频率是否真的会改变 Googlebot 访问网站页面的方式,但这必须留到“有一天”的项目待办事项中。实际上,考虑到数据库中心的 DotNetNuke 站点日志,这可能并不难,并且在较长时期内研究会产生有趣的数据。回到主题……
安装和配置示例 DNN 和博客谷歌站点地图 Provider
如果您已下载代码并希望将其安装到您的网站上,请首先将所有 DLL 文件放入您网站的 \bin 目录中。这包括下载中的 Utility DLLs 和其他相关项。然后,打开您的 web.config 文件并进行以下修改(当然,我不需要提醒您先备份您的 web.config,对吧??)。
在 <configSections>
中,在 <sectionGroup="dotnetnuke"></sectionGroup>
元素下,添加以下条目:
<section name="googlesitemaps"
type= "iFinity.DNN.Modules.GoogleSiteMap.GoogleSiteMapSection,
iFinity.DNN.GoogleSiteMapProvider" />
此条目告知 ASP.NET 当调用内置的 Google Sitemap HttpHandler 时,存在一个名为“googlesitemaps
”的配置节,这就引出了下一个必需的条目,即 HttpHandler。在 <httphandlers>
部分,添加以下条目:
<add verb="*" path= "GoogleSiteMap.axd"
type="iFinity.DNN.Modules.GoogleSiteMap.GoogleSiteMapHandler,
iFinity.DNN.GoogleSiteMapProvider" />
这告诉 ASP.NET 任何对 GoogleSiteMap.axd 的请求都应加载位于 iFinity.DNN.GoogleSiteMapProvider 程序集中的 GoogleSiteMapHandler。在此 Handler 中包含了加载实际 GoogleSiteMap Provider 的代码。
web.config 中的下一个条目是 ASP.NET 在第一个条目中通知的 <googlesitemaps>
部分。这包含了用于提供 Google Sitemap 服务的 Provider 的实际规范。此条目应放置在 web.config 文件的末尾,位于 <dotnetnuke/>
部分下方,以及 </configuration>
部分结束标签之前。
<googlesitemaps defaultProvider="BaseGoogleSitemapProvider">
<providers>
<add name= "BaseGoogleSiteMapProvider"
type="iFinity.DNN.Modules.GoogleSiteMap.GoogleSiteMapProvider"
defaultPagePriority="0.5" defaultPageUpdateFrequency="daily"
includeHiddenPages="false"/>
<add name="View_Blog.GoogleSiteMapProvider"
type="iFinity.DNN.Modules.GoogleSiteMap.BlogGoogleSiteMapProvider,
iFinity.DNN.BlogGoogleSiteMapProvider"
defaultPagePriority="0.5" defaultPageUpdateFrequency="daily"
showArchives="true" includeHiddenPages="false"/>
</providers>
</googlesitemaps>
<googlesitemaps>
元素提供了用于在 DNN 框架中为特定模块放置任何自定义 Provider 的位置。第一个条目是“默认”Provider,是我开发的基于 DNN 的 Google Sitemap Provider。它与 HTTP Handler 在同一个程序集中。defaultPageUpdateFrequency
和 defaultPagePriority
属性告诉 Provider 在 Sitemap XML 中输出什么。还有一个用于指定是否应在 Sitemap 中包含隐藏页面的属性。
第二个条目是针对 Blog 模块的条目。其命名标准与前面关于默认 Provider 如何发现和加载模块特定 Provider 的解释有关。因为名称是“View_Blog.GoogleSiteMapProvider
”(内置命名标准),所以默认 Provider 知道当页面上的模块具有“View_Blog”的 ModuleDefinition 友好名称时,应该调用此特定的 Provider 来生成 Sitemap 条目。由于 BlogGoogleSiteMapProvider
使用 GoogleSiteMapProvider
作为基类,它也具有“defaultPagePriority
”、“defaultPageUpdateFrequency
”和“includeHiddenPages'
”属性。然而,Blog Provider 还添加了一个新的属性,称为“showArchives
”,该属性前面已经介绍过。此属性列表可以无限扩展以满足单个模块的特定需求。
创建您自己的 DNN 模块特定谷歌站点地图 Provider
如果您开发了自己的私有程序集模块来用于 DotNetNuke,并且它使用的不仅仅是标准的页面 URL 来传递内容,那么上述 Provider 模型是为它提供谷歌站点地图的好方法。您只需要创建一个新的程序集,引用 iFinity.DNN.Modules.GoogleSiteMap
Provider 程序集,并将您自己的 Provider 类型从基础 GoogleSiteMapProvider
类型派生出来。然后,您可以根据最合适的方法重新定义 SitePages(siteURL)
方法来索引您的页面。然后,您的自定义 Provider 返回的 SitePage
对象列表将包含在 GoogleSitemapProvider 首先生成并将其转换为符合 Sitemap schema 的 XML 的 SitePage
对象总列表中。
不足之处和潜在扩展
基于 Provider 的谷歌站点地图模型非常简单——但谷歌站点地图本身也具有欺骗性的简单。有些地方可以改进。我还没有长时间使用过这段代码,无法确定该方法是否存在任何重大不足,除了可能在性能方面。但是,考虑到 Googlebot 可能只会在每天一次左右读取站点地图,我认为为了获得方法的灵活性,这是可以接受的权衡。
扩展方面,除了添加更多模块特定的 provider 之外,还可以在基础代码中包含 Sitemap 的 GZip 压缩,因为谷歌允许对 Sitemap 进行 GZip 压缩。它还可以更改为 Sitemap 集合文件,以绕过大型网站(例如列表网站,其中每件待售物品都有自己的 URL)可能面临的 10,000 URL 限制。谷歌允许定义一个站点地图索引文件,该文件然后引用各个站点地图。这可以通过基础 Provider 生成索引文件,然后一系列单独的 Provider 返回它们自己的站点地图文件来完成。我现在没有这方面的需求,但通过修改此处提供的基础代码就可以轻松实现。
摘要
希望这段代码能对其他人有所帮助,因为它本身就是基于一个开源项目和他人工作的。我希望人们发现它是一种将谷歌站点地图集成到自定义 DNN 模块中的有用方法,而无需每次都重写整个站点地图生成代码。也许它甚至可以包含在未来 DNN 的核心版本中,并围绕这个概念创建一个标准供自定义模块 Provider 采用!
更新
我经常更新 DotNetNuke Google Sitemap Provider 的代码,但并不总是更新与本文链接的代码。最新的版本,请访问 iFinity DotNetNuke 谷歌站点地图 Provider 下载页面。