65.9K
CodeProject 正在变化。 阅读更多。
Home

DotNetNuke 站点地图

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.54/5 (15投票s)

2006 年 1 月 8 日

CPOL

6分钟阅读

viewsIcon

300162

downloadIcon

1668

一个适用于 DotNetNuke 4 的网站地图模块,它按需检索数据,对于拥有大量页面的网站仍然非常快速。

引言

我将一个大型网站(超过 500 页)的一部分迁移到了 DotNetNuke,这是一个开源的 .NET CMS。迁移后,每个页面的加载速度非常慢(10 秒!)。经过调查,我发现 SiteMap(一个 TreeView 控件)是造成延迟的原因。因此,我测试了我在网上找到的其他三个 SiteMap 控件。它们的结果都差不多,太慢了。我最初的想法是读取 500 页很耗时。所以我编写了一个测试程序,发现从数据库读取数据很快就完成了,用页面作为节点(带父节点)构建树数据结构也很快就完成了,甚至填充 .NET 2.0 web TreeView 控件也很快。瓶颈在于 MS web TreeView 控件的渲染。我猜想其他较慢的 SiteMap 总是填充整个树,这导致 TreeView 控件渲染时间很长,即使大多数节点都是折叠的。这就是我的想法,加快这个过程,因为你通常只展开树的一小部分,你根本不需要其他叶子节点的数据,因为没有人查看它们。如果用户展开一个节点,那么我就从数据库获取子节点并只填充该节点。DnnSiteMap 的另一个特点是,它能识别它所在的页面(在 DNN 行话中称为 Tab ;-)),并展开到该节点并选择它。

重要提示DnnSitemap 仅适用于 DotNetNuke 4 和 APS.NET 2.0!对于安装,您必须遵循安装说明(见下文)。

背景

DotNetNuke 是一个开源的 .NET 内容管理系统。它源自 IBuySpyPortal,这是 Microsoft 展示 ASP.NET 功能的最佳实践。目前,DotNetNuke (DNN) 的版本是 4,它基于新的 ASP.NET 2.0,并使用 VB.NET 编程。由于其庞大的社区支持,MS 正在支持 DNN 项目。DNN 由 Shaun Walker 领导的核心团队开发。

特点

  • 快速的 SiteMap,因为树只按需从数据库填充。
  • 可以通过设置定义根 Tab(页面)。
  • 可以通过设置显示线条。
  • 可以通过设置节点文本自动换行。
  • 可以通过设置预定义图标集(包含许多)。
  • Panel 控件(全部折叠/全部展开/当前)可以通过设置显示或隐藏。
  • 如果只显示用户有权限查看的选项卡,则标记。
  • 可以通过设置节点缩进像素。

使用代码

我在代码中添加了一些过多的内联注释,以便任何人都能理解每个步骤的作用。基本上,代码是直接的。所有数据和业务逻辑都在 App_Code 文件夹中,UI 代码在 ViewDnnSiteMap.ascx.cs 文件中。

在数据层中,您将找到以下函数

/// <summary>
/// Gets all tabs that have no ParentId 
/// and are not deleted and visible
/// </summary>
/// <returns>IDataReader: TabId (int), TabName (string), 
/// Children (int)</returns>
public abstract IDataReader GetRootNodesFromDb();

/// <summary>
/// Gets all tabs that are children of the specified tab
/// </summary>
/// <param name="parentTabId">TabId of the parent tab</param>
/// <returns>IDataReader: TabId (int), TabName (string), 
/// Children (int)</returns>
public abstract IDataReader GetChildNodesFromDb(int parentTabId);

/// <summary>
/// Gets parent tab for specified tab
/// </summary>
/// <param name="childTabId">TabId of the child tab</param>
/// <returns>IDataReader: ParentTabId (int), ParentName (string);
/// (should be max one row)</returns>
public abstract IDataReader GetParentFromDb(int childTabId);

/// <summary>
/// Gets the Tab, that hosts the given module
/// </summary>
/// <param name="tabModuleId">TabModuleId of the module</param>
/// <returns>IDataReader: ParentTabId (int), ParentName (string);
/// (should be max one row)</returns>
public abstract IDataReader GetTabViaTabModuleIdFromDb(
                                         int tabModuleId);

/// <summary>
/// Gets node with specified TabId from Db
/// </summary>
/// <param name="nodeTabId">TabId for node</param>
/// <returns>IDataReader: TabId (int), TabName (string), 
/// Children (int)</returns>
public abstract IDataReader GetNodeFromDb(int nodeTabId);

您可以在 SqlDataProvider.cs 文件中找到这些函数的实现。它们基本上是简单的 SQL SELECT 语句。在未来的版本中,它们将放在存储过程中,以获得额外的性能。

业务层可以在 DnnSiteMapController.cs 中的控制器类中找到。函数是

/// <summary>
/// Retrieves all visible root nodes from Db
/// </summary>
/// <returns>List of root nodes as ExtendedTreeNode
/// </returns>
public List<ExtendedTreeNode> GetRootNodesFromDb()

/// <summary>
/// Retrieves Child Nodes from Db for given Node
/// </summary>
/// <param name="parentNode">ParentNode, 
/// for which the children should be retrieved</param>
/// <returns>List of children as ExtendedTreeNode
/// </returns>
public List<ExtendedTreeNode> GetChildNodesFromDb(
                                       TreeNode parentNode)

/// <summary>
/// Gets the navigation path for a given Tab to the root
/// </summary>
/// <param name="childTab">Tab for 
/// which the path should be retrieved</param>
/// <returns>List of Tabs, begining with the root 
/// and ending with the Child</returns>
public List<Structs.Tab> GetNavigationPathFromDb(
                                      Structs.Tab childTab)

/// <summary>
/// Gets the Tab, that hosts the given module
/// </summary>
/// <param name="tabModuleId">TabModuleId of the module
/// </param>
/// <returns>Dnn TabId</returns>
public Structs.Tab GetTabViaTabModuleIdFromDb(int tabModuleId)

/// <summary>
/// Gets node with specified TabId from Db
/// </summary>
/// <param name="nodeTabId">TabId for node</param>
/// <returns>Specified node; null if node is not found
/// </returns>
public ExtendedTreeNode GetNodeFromDb(int nodeTabId)

UI 代码在 ViewDnnSiteMap.ascx.cs 文件中。在 Page_Load 事件中,应用设置并从数据库检索根节点。然后,树展开到当前选项卡(托管控件的页面)

protected void Page_Load(System.Object sender, 
                               System.EventArgs e)
{
    try
    {
        if (!IsPostBack)
        {
            // controller class
            DnnSiteMapController objDnnSiteMaps = 
                           new DnnSiteMapController();

            // config settings
            ConfigurationSettings settings = 
                 new ConfigurationSettings(this.Settings);

            // set show lines
            this.TreeView1.ShowLines = settings.ShowLines;

            // set image set
            this.TreeView1.ImageSet = settings.ImageSet;

            // set node wrap
            this.TreeView1.NodeWrap = settings.NodeWrap;

            // set show controls
            this.pnlControls.Visible = settings.ShowControls;

            // set node indent
            this.TreeView1.NodeIndent = settings.NodeIndent;

            // fill root nodes or specified rootNode
            this.FillRootNodes(settings.RootNode);

            // get current TabId from DNN and expand to it
            this.ExpandToTab(this.TabId);
        }
    }
    catch (Exception exc) //Module failed to load
    {
        Exceptions.ProcessModuleLoadException(this, exc);
    }
}

TreeNodeExpanded 事件中,我检查节点是否已经有其子节点,如果没有,我从数据库中检索它们

protected void TreeView1_TreeNodeExpanded(object sender, 
                                       TreeNodeEventArgs e)
{
    // if node has DummyNode, else data was 
    // already retrieved from Db
    if (NodeHelper.HasDummyNode(e.Node))
    {
        // controller class
        DnnSiteMapController objDnnSiteMaps = 
                         new DnnSiteMapController();

        // clear child nodes
        e.Node.ChildNodes.Clear();

        // for all child nodes
        foreach (ExtendedTreeNode childNode in 
                 objDnnSiteMaps.GetChildNodesFromDb(e.Node))
        {
            // if root has children, add dummy node
            if (childNode.HasChildren)
            {
                NodeHelper.AddDummyNode(childNode);
            }

            // add children to expanded node
            e.Node.ChildNodes.Add(childNode);
        }
    }

    // select current node
    this.SelectCurrentNode();
}

private ExpandToTab(int tabId) 方法用于 Page_Load 事件。此方法用于将树展开到指定的节点并选择它。这在 Page_Load 事件中非常方便,因为您可以将选项卡设置为当前页面

private void ExpandToTab(int tabId)
{
    // controller class
    DnnSiteMapController objDnnSiteMaps = 
                      new DnnSiteMapController();

    // collapses all nodes; IMPORTANT to use this function
    // instead directly TreeView.CollapseAll(), because
    // it can loose all nodes
    this.CollapseAll();

    // find node in tree view (no roundtrip to Db)
    TreeNode node = 
        NodeHelper.GetNode(this.TreeView1.Nodes, tabId);

    // check if node is already in tree view
    if (node != null)
    {
        TreeNode currentNode = node;

        // expand to node
        while (currentNode != null)
        {
            currentNode.Expand();
            currentNode = currentNode.Parent;
        }
    }
    else // get parent path from Db
    {
        List<Structs.Tab> parentTabs = 
            objDnnSiteMaps.GetNavigationPathFromDb(
                 new Structs.Tab(tabId, String.Empty));
        TreeNode currentNode = null;

        // expand all nodes along path
        foreach (Structs.Tab nodeTab in parentTabs)
        {
            currentNode = 
              NodeHelper.GetNode(this.TreeView1.Nodes, 
                                         nodeTab.TabId);

            if (currentNode != null)
            {
                currentNode.Expand();
            }
        }
    }

    // select current node
    this.SelectCurrentNode();
}

关注点

DNN 安装说明

步骤 1:通过 DNN 安装模块

以 host 身份登录,并在 Host 菜单中选择“模块定义”。在页面底部,按“上传新模块”。然后,选择 .zip 文件,添加它,然后上传。

步骤 2:修改 web.config 文件(否则 DNN 将无法工作)

该模块是用 C# 编写的,您必须web.config 文件的 <system.web> <compilation> 部分中包含以下行(它应该已经存在,但被注释掉了)

<codeSubDirectories>
   <add directoryName="DnnSiteMap"/>
</codeSubDirectories>
TreeNode 类的 Value 属性

由于 DNN 中的每个选项卡(页面)都有一个 TabId,所以我将该信息与每个 TreeNode 一起保存。为此,我使用 value 字段。它的类型是 string,所以,我会在需要时将其转换为 int

ExtendedNode 类

ExtendedNode 类直接派生自 System.Web.UI.WebControls.TreeNode 类。它为该类添加了布尔字段 HasChildren。当我从数据库中检索节点时,如果它们有子节点,我就会将该标志设置为 true。通过这个小技巧,我可以节省一次额外的数据库往返。

DummyNodes

TreeView 控件在节点至少有一个子节点时显示一个加号以展开节点。因为我不想在父节点展开时才检索节点,所以我用 DummyNode 填充它们。为了与普通节点区分开来,我将其 value 字段设置为 -1

ConfigSettings

我将设置封装在 ConfigurationSettings 类中。它从 Hashtable(例如,ModuleSettingsBase.TabModuleSettingsPortalModuleBase.Settings)中读取设置,使它们类型安全,并用默认值初始化它们。

路线图

新功能可以是:使用存储过程;定义自定义 CSS 类 .... 所以,还有很多工作要做。

Copyright

DNNSiteMap, 版权所有 (c) 2006 BitConstruct, Halanek & Co KEG (BitConstruct)。

特此免费授予任何获得本软件及相关文档文件(“软件”)副本的人处理本软件的权利,不受限制,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件副本的权利,并允许接收软件的人这样做,但须遵守以下条件

上述版权声明和本许可声明应包含在软件的所有副本或重要部分中。

本软件“按原样”提供,不附带任何明示或暗示的保证,包括但不限于适销性、特定用途适用性和非侵权性的保证。在任何情况下,作者或版权所有者均不对任何索赔、损害或其他责任承担责任,无论是因合同、侵权或其他原因引起的,与软件或软件的使用或其他交易有关或由此产生的。

历史

  • 2006/01/08 - V 0.1
    • 概念验证,适用于生产环境 (!)。
  • 2006/01/13 - V 1.0
    • 工作版本,包含许多新功能(仅限 DNN 4 和 ASP.NET 2.0)
  • 2006/01/28 - V 1.0.1
    • 小错误修复:现在使用数据库对象的 ObjectQualifier
  • 2006/04/24 - V 1.0.2
    • 新标志,仅显示用户有权限查看的选项卡。
    • 可以关闭突出显示当前节点。
  • 2006/05/07 - V 1.0.3
    • 如果主机设置中启用了友好 URL,DNNSitemap 现在使用友好 URL。
© . All rights reserved.