public static IEnumerable<SiteMapNode> BuildDynamicNodes(string assemblyQualifiedTypeString, SiteMapNode parentNode)
        {
            var type = Type.GetType(assemblyQualifiedTypeString);
            if (type == null)
                throw new Exception($"Type {assemblyQualifiedTypeString} was not found.");

            IDynamicNodeProvider dynamicNodeProvider;
            if (!cachedNodeProviders.TryGetValue(type, out dynamicNodeProvider))
            {
                lock (syncLock)
                {
                    if (!cachedNodeProviders.TryGetValue(type, out dynamicNodeProvider))
                    {
                        dynamicNodeProvider = Activator.CreateInstance(type) as IDynamicNodeProvider;

                        cachedNodeProviders.Add(type, dynamicNodeProvider);
                    }
                }
            }

            foreach(var dynamicNode in dynamicNodeProvider.GetSiteMapNodes())
            {
                yield return ProcessNode(dynamicNode, parentNode);
            }
        }
        private static SiteMapNode ProcessNode(SiteMapNode nodeToProcess, SiteMapNode parentNode)
        {
            var area = string.IsNullOrEmpty(nodeToProcess.Area) ? parentNode.Area : nodeToProcess.Area;
            var controller = string.IsNullOrEmpty(nodeToProcess.Controller) ? parentNode.Controller : nodeToProcess.Controller;
            var action = nodeToProcess.Action;
            var url = nodeToProcess.Url;
            var explicitKey = nodeToProcess.Key;
            var parentKey = parentNode == null ? "" : parentNode.Key;
            var httpMethod = HttpVerbs.Get.ToString().ToUpperInvariant();
            var clickable = true;
            var title = nodeToProcess.Title;

            string key = nodeKeyGenerator.GenerateKey(
                parentKey,
                explicitKey,
                url,
                title,
                area,
                controller,
                action,
                httpMethod,
                clickable
            );

            nodeToProcess.Area = area;
            nodeToProcess.Controller = controller;
            nodeToProcess.Action = action;
            nodeToProcess.Url = url;
            nodeToProcess.UnresolvedUrl = url;
            nodeToProcess.Key = key;
            nodeToProcess.Clickable = clickable;
            nodeToProcess.Title = title;

            return nodeToProcess;
        }
        protected virtual string InheritAreaIfNotProvided(SiteMapNode node, SiteMapNode parentNode)
        {
            var result = node.Area;
            if (string.IsNullOrEmpty(result) && parentNode != null)
            {
                result = parentNode.Area;
            }

            return result;
        }
        protected virtual SiteMapNode GetSiteMapNodeFromXmlElement(XElement node, SiteMapNode parentNode)
        {
            // get data required to generate the node instance

            // get area and controller from node declaration
            var area = InheritAreaIfNotProvided(node, parentNode);
            var controller = InheritControllerIfNotProvided(node, parentNode);
            var action = node.GetAttributeValue("action");
            var url = node.GetAttributeValue("url");
            var explicitKey = node.GetAttributeValue("key");
            var parentKey = parentNode == null ? string.Empty : parentNode.Key;
            var httpMethod = node.GetAttributeValueOrFallback("httpMethod", HttpVerbs.Get.ToString()).ToUpperInvariant();
            var clickable = bool.Parse(node.GetAttributeValueOrFallback("clickable", "true"));
            var title = node.GetAttributeValue("title");
            var description = node.GetAttributeValue("description");
            var targetFrame = node.GetAttributeValue("targetFrame");
            var imageUrl = node.GetAttributeValue("imageUrl");
            var order = int.Parse(node.GetAttributeValueOrFallback("order", "0"));
            var dynamicNodeProvider = node.GetAttributeValue("dynamicNodeProvider");
            //var implicitResourceKey = node.GetAttributeValue("resourceKey");

            var siteMapNode = new SiteMapNode
            {
                Key = nodeKeyGenerator.GenerateKey(parentKey, explicitKey, url, title, area, controller, action, httpMethod, clickable),
                Title = title,
                Clickable = clickable,
                Area = area,
                Controller = controller,
                Action = action,
                DynamicNodeProvider = dynamicNodeProvider,
                Attributes = new Dictionary<string, object>(),
                ChildNodes = new List<SiteMapNode>(),
                Description = description,
                TargetFrame = targetFrame,
                ImageUrl = imageUrl,
                Url = url,
                UnresolvedUrl = url,
                Order = order
            };

            return siteMapNode;
        }
        protected virtual SiteMapNode GetSiteMapNodeFromJSONNode(SiteMapNode jsonNode, SiteMapNode parentNode)
        {
            // get data required to generate the node instance

            // get area and controller
            var area = InheritAreaIfNotProvided(jsonNode, parentNode);
            var controller = InheritControllerIfNotProvided(jsonNode, parentNode);
            var action = jsonNode.Action;
            var url = jsonNode.Url;
            var explicitKey = jsonNode.Key;
            var parentKey = parentNode == null ? string.Empty : parentNode.Key;
            var httpMethod = (string.IsNullOrEmpty(jsonNode.HttpMethod) ? HttpVerbs.Get.ToString() : jsonNode.HttpMethod).ToUpperInvariant();
            var clickable = jsonNode.Clickable;
            var title = jsonNode.Title;
            var description = jsonNode.Description;
            var targetFrame = jsonNode.TargetFrame;
            var imageUrl = jsonNode.ImageUrl;
            var order = jsonNode.Order;
            var dynamicNodeProvider = jsonNode.DynamicNodeProvider;

            var siteMapNode = new SiteMapNode
            {
                Key = nodeKeyGenerator.GenerateKey(parentKey, explicitKey, url, title, area, controller, action, httpMethod, clickable),
                Title = title,
                Clickable = clickable,
                Area = area,
                Controller = controller,
                Action = action,
                DynamicNodeProvider = dynamicNodeProvider,
                Attributes = new Dictionary<string, object>(),
                ChildNodes = new List<SiteMapNode>(),
                Description = description,
                TargetFrame = targetFrame,
                ImageUrl = imageUrl,
                Url = url,
                UnresolvedUrl = url,
                Order = order
            };

            return siteMapNode;
        }
 /// <summary>
 /// Build a menu, based on the MvcSiteMap
 /// </summary>
 /// <param name="helper">The helper.</param>
 /// <param name="startingNode">The starting node.</param>
 /// <param name="startingNodeInChildLevel">Show starting node in child level if set to <c>true</c>.</param>
 /// <param name="showStartingNode">Show starting node if set to <c>true</c>.</param>
 /// <param name="maxDepth">The max depth.</param>
 /// <returns>Html markup</returns>
 public static MvcHtmlString Menu(this MvcSiteMapHtmlHelper helper, SiteMapNode startingNode, bool startingNodeInChildLevel, bool showStartingNode, int maxDepth)
 {
     return Menu(helper, startingNode, startingNodeInChildLevel, showStartingNode, maxDepth, false);
 }
 /// <summary>
 /// Build a menu, based on the MvcSiteMap
 /// </summary>
 /// <param name="helper">The helper.</param>
 /// <param name="startingNode">The starting node.</param>
 /// <param name="startingNodeInChildLevel">Show starting node in child level if set to <c>true</c>.</param>
 /// <param name="showStartingNode">Show starting node if set to <c>true</c>.</param>
 /// <param name="maxDepth">The max depth.</param>
 /// <param name="drillDownToCurrent">Should the model exceed the maxDepth to reach the current node?</param>
 /// <param name="sourceMetadata">User-defined meta data.</param>
 /// <returns>Html markup</returns>
 public static MvcHtmlString Menu(this MvcSiteMapHtmlHelper helper, SiteMapNode startingNode, bool startingNodeInChildLevel, bool showStartingNode, int maxDepth, bool drillDownToCurrent, object sourceMetadata)
 {
     return Menu(helper, null, startingNode, startingNodeInChildLevel, showStartingNode, maxDepth, drillDownToCurrent, sourceMetadata);
 }
 /// <summary>
 /// Builds the model.
 /// </summary>
 /// <param name="helper">The helper.</param>
 /// <param name="sourceMetadata">User-defined meta data.</param>
 /// <param name="startingNode">The starting node.</param>
 /// <param name="startingNodeInChildLevel">Renders startingNode in child level if set to <c>true</c>.</param>
 /// <param name="showStartingNode">Show starting node if set to <c>true</c>.</param>
 /// <param name="maxDepth">The max depth.</param>
 /// <returns>The model.</returns>
 private static MenuHelperModel BuildModel(MvcSiteMapHtmlHelper helper, SourceMetadataDictionary sourceMetadata, SiteMapNode startingNode, bool startingNodeInChildLevel, bool showStartingNode, int maxDepth)
 {
     return BuildModel(helper, sourceMetadata, startingNode, startingNodeInChildLevel, showStartingNode, maxDepth, false, helper.SiteMap.VisibilityAffectsDescendants);
 }
 /// <summary>
 /// Gets SiteMap path for the current request
 /// </summary>
 /// <param name="helper">MvcSiteMapHtmlHelper instance</param>
 /// <param name="startingNode">The starting node (the last node in the site map path).</param>
 /// <returns>SiteMap path for the current request</returns>
 public static MvcHtmlString SiteMapPath(this MvcSiteMapHtmlHelper helper, SiteMapNode startingNode)
 {
     return SiteMapPath(helper, null, startingNode);
 }
 /// <summary>
 /// Gets SiteMap path for the current request
 /// </summary>
 /// <param name="helper">MvcSiteMapHtmlHelper instance</param>
 /// <param name="templateName">Name of the template.</param>
 /// <param name="startingNode">The starting node (the last node in the site map path).</param>
 /// <param name="sourceMetadata">User-defined meta data.</param>
 /// <returns>SiteMap path for the current request</returns>
 public static MvcHtmlString SiteMapPath(this MvcSiteMapHtmlHelper helper, string templateName, SiteMapNode startingNode, SourceMetadataDictionary sourceMetadata)
 {
     var model = BuildModel(helper, GetSourceMetadata(sourceMetadata), startingNode);
     return helper
         .CreateHtmlHelperForModel(model)
         .DisplayFor(m => model, templateName);
 }
 /// <summary>
 /// Build a menu, based on the MvcSiteMap
 /// </summary>
 /// <param name="helper">The helper.</param>
 /// <param name="startingNode">The starting node.</param>
 /// <param name="startingNodeInChildLevel">Show starting node in child level if set to <c>true</c>.</param>
 /// <param name="showStartingNode">Show starting node if set to <c>true</c>.</param>
 /// <returns>Html markup</returns>
 public static MvcHtmlString Menu(this MvcSiteMapHtmlHelper helper, SiteMapNode startingNode, bool startingNodeInChildLevel, bool showStartingNode)
 {
     return Menu(helper, startingNode, startingNodeInChildLevel, showStartingNode, Int32.MaxValue, false);
 }
 /// <summary>
 /// Build a menu, based on the MvcSiteMap
 /// </summary>
 /// <param name="helper">The helper.</param>
 /// <param name="templateName">Name of the template.</param>
 /// <param name="startingNode">The starting node.</param>
 /// <param name="startingNodeInChildLevel">Show starting node in child level if set to <c>true</c>.</param>
 /// <param name="showStartingNode">Show starting node if set to <c>true</c>.</param>
 /// <param name="maxDepth">The max depth.</param>
 /// <param name="sourceMetadata">User-defined meta data.</param>
 /// <returns>Html markup</returns>
 public static MvcHtmlString Menu(this MvcSiteMapHtmlHelper helper, string templateName, SiteMapNode startingNode, bool startingNodeInChildLevel, bool showStartingNode, int maxDepth, object sourceMetadata)
 {
     return Menu(helper, templateName, startingNode, startingNodeInChildLevel, showStartingNode, maxDepth, false, sourceMetadata);
 }
        protected virtual void ProcessNodes(IEnumerable<SiteMapNode> nodes, SiteMapNode rootNode)
        {
            foreach (var node in nodes)
            {
                var siteMapNode = GetSiteMapNodeFromJSONNode(node, rootNode);

                if (!string.IsNullOrEmpty(siteMapNode.DynamicNodeProvider))
                {
                    // has dynamic node provider
                    var dynamicNodes = DynamicNodeBuilder.BuildDynamicNodes(siteMapNode.DynamicNodeProvider, rootNode);

                    // add to root node
                    rootNode.ChildNodes.AddRange(dynamicNodes);

                    // add non-dynamic children for every dynamic node
                    ProcessNodes(dynamicNodes, rootNode);
                }
                else
                {
                    rootNode.ChildNodes.Add(siteMapNode);

                    ProcessNodes(node.ChildNodes, siteMapNode);
                }
            }
        }
        /// <summary>
        /// Recursively process our XML document, parsing our siteMapNodes and dynamicNode(s).
        /// </summary>
        /// <param name="rootNode">The main root siteMap node.</param>
        /// <param name="rootElement">the main root XML element.</param>
        protected virtual void ProcessXmlNodes(SiteMapNode rootNode, XElement rootElement)
        {
            // loop through each element below the current root element
            foreach (XElement node in rootElement.Elements())
            {
                SiteMapNode childNode;
                if (!node.Name.LocalName.Equals(nodeName, StringComparison.OrdinalIgnoreCase))
                    throw new Exception($"Element of type {node.Name.LocalName} is an invalid node.");

                // if this is a normal mvcSieMapNode then map the xml element
                // to an mvcSiteMapNode, and add the node to the current root.
                childNode = GetSiteMapNodeFromXmlElement(node, rootNode);

                if (!string.IsNullOrEmpty(childNode.DynamicNodeProvider))
                {
                    // has dynamic node provider
                    var dynamicNodes = DynamicNodeBuilder.BuildDynamicNodes(childNode.DynamicNodeProvider, rootNode);

                    // add to root node
                    rootNode.ChildNodes.AddRange(dynamicNodes);

                    // add non-dynamic children for every dynamic node
                    foreach (var dynamicNode in dynamicNodes)
                    {
                        ProcessXmlNodes(dynamicNode, node);
                    }
                }
                else
                {
                    rootNode.ChildNodes.Add(childNode);

                    ProcessXmlNodes(childNode, node);
                }
            }
        }
        /// <summary>
        /// Inherits the controller from the parent node if it is not provided in the current siteMapNode XML element and the parent node is not null.
        /// </summary>
        /// <param name="node">The siteMapNode element.</param>
        /// <param name="parentNode">The parent node.</param>
        /// <returns>The value provided by either the siteMapNode or parentNode.Controller.</returns>
        protected virtual string InheritControllerIfNotProvided(XElement node, SiteMapNode parentNode)
        {
            var result = node.GetAttributeValue("controller");
            if (node.Attribute("controller") == null && parentNode != null)
            {
                result = parentNode.Controller;
            }

            return result;
        }
 /// <summary>
 /// Build a menu, based on the MvcSiteMap
 /// </summary>
 /// <param name="helper">The helper.</param>
 /// <param name="startingNode">The starting node.</param>
 /// <param name="startingNodeInChildLevel">Show starting node in child level if set to <c>true</c>.</param>
 /// <param name="showStartingNode">Show starting node if set to <c>true</c>.</param>
 /// <param name="maxDepth">The max depth.</param>
 /// <param name="sourceMetadata">User-defined meta data.</param>
 /// <returns>Html markup</returns>
 public static MvcHtmlString Menu(this MvcSiteMapHtmlHelper helper, SiteMapNode startingNode, bool startingNodeInChildLevel, bool showStartingNode, int maxDepth, SourceMetadataDictionary sourceMetadata)
 {
     return Menu(helper, startingNode, startingNodeInChildLevel, showStartingNode, maxDepth, false, sourceMetadata);
 }
 /// <summary>
 /// Build a menu, based on the MvcSiteMap
 /// </summary>
 /// <param name="helper">The helper.</param>
 /// <param name="templateName">Name of the template.</param>
 /// <param name="startingNode">The starting node.</param>
 /// <param name="startingNodeInChildLevel">Show starting node in child level if set to <c>true</c>.</param>
 /// <param name="showStartingNode">Show starting node if set to <c>true</c>.</param>
 /// <param name="sourceMetadata">User-defined meta data.</param>
 /// <returns>Html markup</returns>
 public static MvcHtmlString Menu(this MvcSiteMapHtmlHelper helper, string templateName, SiteMapNode startingNode, bool startingNodeInChildLevel, bool showStartingNode, SourceMetadataDictionary sourceMetadata)
 {
     return Menu(helper, templateName, startingNode, startingNodeInChildLevel, showStartingNode, Int32.MaxValue, false, sourceMetadata);
 }
 /// <summary>
 /// Build a menu, based on the MvcSiteMap
 /// </summary>
 /// <param name="helper">The helper.</param>
 /// <param name="templateName">Name of the template.</param>
 /// <param name="startingNode">The starting node.</param>
 /// <param name="startingNodeInChildLevel">Show starting node in child level if set to <c>true</c>.</param>
 /// <param name="showStartingNode">Show starting node if set to <c>true</c>.</param>
 /// <param name="maxDepth">The max depth.</param>
 /// <param name="drillDownToCurrent">Should the model exceed the maxDepth to reach the current node</param>
 /// <returns>Html markup</returns>
 public static MvcHtmlString Menu(this MvcSiteMapHtmlHelper helper, string templateName, SiteMapNode startingNode, bool startingNodeInChildLevel, bool showStartingNode, int maxDepth, bool drillDownToCurrent)
 {
     return Menu(helper, templateName, startingNode, startingNodeInChildLevel, showStartingNode, maxDepth, drillDownToCurrent, new SourceMetadataDictionary());
 }
 /// <summary>
 /// Gets SiteMap path for the current request
 /// </summary>
 /// <param name="helper">MvcSiteMapHtmlHelper instance</param>
 /// <param name="templateName">Name of the template.</param>
 /// <param name="startingNode">The starting node (the last node in the site map path).</param>
 /// <param name="sourceMetadata">User-defined meta data.</param>
 /// <returns>SiteMap path for the current request</returns>
 public static MvcHtmlString SiteMapPath(this MvcSiteMapHtmlHelper helper, string templateName, SiteMapNode startingNode, object sourceMetadata)
 {
     return SiteMapPath(helper, templateName, startingNode, new SourceMetadataDictionary(sourceMetadata));
 }
 /// <summary>
 /// Build a menu, based on the MvcSiteMap
 /// </summary>
 /// <param name="helper">The helper.</param>
 /// <param name="templateName">Name of the template.</param>
 /// <param name="startingNode">The starting node.</param>
 /// <param name="startingNodeInChildLevel">Show starting node in child level if set to <c>true</c>.</param>
 /// <param name="showStartingNode">Show starting node if set to <c>true</c>.</param>
 /// <param name="maxDepth">The max depth.</param>
 /// <param name="drillDownToCurrent">Should the model exceed the maxDepth to reach the current node</param>
 /// <param name="visibilityAffectsDescendants"><c>true</c> if the visibility provider should affect the visibility of descendant nodes, otherwise <c>false</c>.</param>
 /// <param name="sourceMetadata">User-defined meta data.</param>
 /// <returns>Html markup</returns>
 public static MvcHtmlString Menu(this MvcSiteMapHtmlHelper helper, string templateName, SiteMapNode startingNode, bool startingNodeInChildLevel, bool showStartingNode, int maxDepth, bool drillDownToCurrent, bool visibilityAffectsDescendants, object sourceMetadata)
 {
     return Menu(helper, templateName, startingNode, startingNodeInChildLevel, showStartingNode, maxDepth, drillDownToCurrent, visibilityAffectsDescendants, new SourceMetadataDictionary(sourceMetadata));
 }
        /// <summary>
        /// Builds the model.
        /// </summary>
        /// <param name="helper">The helper.</param>
        /// <param name="startingNode">The starting node.</param>
        /// <param name="sourceMetadata">User-defined meta data.</param>
        /// <returns>The model.</returns>
        private static SiteMapPathHelperModel BuildModel(MvcSiteMapHtmlHelper helper, SourceMetadataDictionary sourceMetadata, SiteMapNode startingNode)
        {
            // Build model
            var model = new SiteMapPathHelperModel();
            var node = startingNode;
            while (node != null)
            {
                bool nodeVisible = node.IsVisible(sourceMetadata);
                if (nodeVisible && node.IsAccessibleToUser())
                {
                    var nodeToAdd = new SiteMapNodeModel(helper.SiteMap, node, sourceMetadata);
                    model.Nodes.Add(nodeToAdd);
                }
                node = node.GetParentNode(helper.SiteMap);
            }
            model.Nodes.Reverse();

            return model;
        }
 /// <summary>
 /// Build a menu, based on the MvcSiteMap
 /// </summary>
 /// <param name="helper">The helper.</param>
 /// <param name="templateName">Name of the template.</param>
 /// <param name="startingNode">The starting node.</param>
 /// <param name="startingNodeInChildLevel">Show starting node in child level if set to <c>true</c>.</param>
 /// <param name="showStartingNode">Show starting node if set to <c>true</c>.</param>
 /// <param name="maxDepth">The max depth.</param>
 /// <param name="drillDownToCurrent">Should the model exceed the maxDepth to reach the current node</param>
 /// <param name="visibilityAffectsDescendants"><c>true</c> if the visibility provider should affect the visibility of descendant nodes, otherwise <c>false</c>.</param>
 /// <param name="sourceMetadata">User-defined meta data.</param>
 /// <returns>Html markup</returns>
 public static MvcHtmlString Menu(this MvcSiteMapHtmlHelper helper, string templateName, SiteMapNode startingNode, bool startingNodeInChildLevel, bool showStartingNode, int maxDepth, bool drillDownToCurrent, bool visibilityAffectsDescendants, SourceMetadataDictionary sourceMetadata)
 {
     var model = BuildModel(helper, GetSourceMetadata(sourceMetadata), startingNode, startingNodeInChildLevel, showStartingNode, maxDepth, drillDownToCurrent, visibilityAffectsDescendants);
     return helper
         .CreateHtmlHelperForModel(model)
         .DisplayFor(m => model, templateName);
 }
Example #23
0
        private TreeNode<MenuItem> ConvertSitemapNodeToMenuItemNode(SiteMapNode node)
        {
            var item = new MenuItem();
            var treeNode = new TreeNode<MenuItem>(item);

            if (node.RouteName.HasValue())
            {
                item.RouteName = node.RouteName;
            }
            else if (node.ActionName.HasValue() && node.ControllerName.HasValue())
            {
                item.ActionName = node.ActionName;
                item.ControllerName = node.ControllerName;
            }
            else if (node.Url.HasValue())
            {
                item.Url = node.Url;
            }
            item.RouteValues = node.RouteValues;
            
            item.Visible = node.Visible;
            item.Text = node.Title;
            item.Attributes.Merge(node.Attributes);

            if (node.Attributes.ContainsKey("permissionNames"))
                item.PermissionNames = node.Attributes["permissionNames"] as string;

            if (node.Attributes.ContainsKey("id"))
                item.Id = node.Attributes["id"] as string;

            if (node.Attributes.ContainsKey("resKey"))
                item.ResKey = node.Attributes["resKey"] as string;

			if (node.Attributes.ContainsKey("iconClass"))
				item.Icon = node.Attributes["iconClass"] as string;

            if (node.Attributes.ContainsKey("imageUrl"))
                item.ImageUrl = node.Attributes["imageUrl"] as string;

            if (node.Attributes.ContainsKey("isGroupHeader"))
                item.IsGroupHeader = Boolean.Parse(node.Attributes["isGroupHeader"] as string);

            // iterate children recursively
            foreach (var childNode in node.ChildNodes)
            {
                var childTreeNode = ConvertSitemapNodeToMenuItemNode(childNode);
                treeNode.Append(childTreeNode);
            }
            
            return treeNode;
        }
        /// <summary>
        /// Builds the model.
        /// </summary>
        /// <param name="helper">The helper.</param>
        /// <param name="sourceMetadata">User-defined meta data.</param>
        /// <param name="startingNode">The starting node.</param>
        /// <param name="startingNodeInChildLevel">Renders startingNode in child level if set to <c>true</c>.</param>
        /// <param name="showStartingNode">Show starting node if set to <c>true</c>.</param>
        /// <param name="maxDepth">The max depth.</param>
        /// <param name="drillDownToCurrent">Should the model exceed the maxDepth to reach the current node?</param>
        /// <param name="visibilityAffectsDescendants"><b>true</b> if the visibility provider should affect the current node as well as all descendant nodes; otherwise <b>false</b>.</param>
        /// <returns>The model.</returns>
        internal static MenuHelperModel BuildModel(MvcSiteMapHtmlHelper helper, SourceMetadataDictionary sourceMetadata, SiteMapNode startingNode, bool startingNodeInChildLevel, bool showStartingNode, int maxDepth, bool drillDownToCurrent, bool visibilityAffectsDescendants)
        {
            // Build model
            var model = new MenuHelperModel();
            var node = startingNode;

            // Check if a starting node has been given
            if (node == null)
            {
                return model;
            }

            // Check ACL
            if (node.IsAccessibleToUser())
            {
                // Add node?
                var nodeToAdd = new SiteMapNodeModel(helper.SiteMap, node, sourceMetadata, maxDepth, drillDownToCurrent, startingNodeInChildLevel, visibilityAffectsDescendants);

                // Check visibility
                if (node.IsVisible(sourceMetadata))
                {
                    if (showStartingNode || !startingNodeInChildLevel)
                    {
                        model.Nodes.Add(nodeToAdd);
                    }
                    // Add child nodes
                    if (visibilityAffectsDescendants && startingNodeInChildLevel)
                    {
                        model.Nodes.AddRange(nodeToAdd.Children);
                    }
                }
                // Add child nodes
                if (!visibilityAffectsDescendants && startingNodeInChildLevel)
                {
                    model.Nodes.AddRange(nodeToAdd.Children);
                }
            }

            return model;
        }