Dynamic Children miss parent data

Mar 24, 2011 at 5:00 PM
Edited Mar 25, 2011 at 3:47 PM

I've moved my whole site navigation into one control set.  Previously I had a navigation section in each area of the site.
It made for a very lengthy mvc.sitemap.

Now I've got all the navigation running with 4 Dynamic Node providers. 

 

<mvcSiteMapNode dynamicNodeProvider="nvpMVC.Code.AreaDynamicNodeProvider, nvpMVC">
	<mvcSiteMapNode dynamicNodeProvider="nvpMVC.Code.ControllerDynamicNodeProvider, nvpMVC">
		<mvcSiteMapNode dynamicNodeProvider="nvpMVC.Code.ActionDynamicNodeProvider, nvpMVC">
			<mvcSiteMapNode dynamicNodeProvider="nvpMVC.Code.MethodDynamicNodeProvider, nvpMVC"/>
		</mvcSiteMapNode>
	</mvcSiteMapNode>
</mvcSiteMapNode>

 

In each provider I set the appropriate control.  Area sets area, Controller sets controller, etc.
However, the children don't know the parent's information.  All the nodes are displayed in the correct hierarchy,
but they don't have the proper routing information.  The Controller children don't have an area set.
If children can't get parent information from the sitemap hierarchy, why am I even creating a nested sitemap? 

I really don't want to have to pull the full path for each sub provider, as that's a whole lot of work that doesn't seem necessary. 

Mar 25, 2011 at 10:59 PM

Area should be inherited from the routedictionary in the parent. If you specify it manually, the manual setting should be applied.

Mar 26, 2011 at 1:12 AM

In dynamicNodeProvider, you must set distinct key for node. And in children, every node must refer to correct parent key

Mar 28, 2011 at 3:11 PM
Edited Mar 28, 2011 at 4:51 PM

The route dictionary has values only where I specify them.  Although I think my routing may be off.  I'll take a look at that.
Each node Key is set properly, and each child does have the correct parent key.

So I've played around with some of my route tables and I'm noticing some strange behaviour, hoping someone can clear it up.

node.Area = parent_value;
node.Controller = value;

This produces the correct tree, but no propagation of values.  If I try with RouteValues:

node.RouteValues.Add("Area", parent_value);
node.RouteValues.Add("Controller", value); 

This produces a different result.  I get correct URL values but I'm missing parts of the navigation tree.
I'm very confused..... 

Mar 29, 2011 at 8:06 PM

Ok, now I've got myself completely lost.  I'm absolutely stumped about this one.  I simply cannot seem to get the area value to pass down even one level of navigation.
I've gone as simple as possible to eliminate any issues.

<?xml version="1.0" encoding="utf-8" ?>
<mvcSiteMap xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-2.0" enableLocalization="true">
	<mvcSiteMapNode title="$Resources:Strings,NavHome" controller="Home" action="Index" key="Root">
		<mvcSiteMapNode dynamicNodeProvider="nvpMVC.Code.AreaDynamicNodeProvider, nvpMVC">
			<mvcSiteMapNode dynamicNodeProvider="nvpMVC.Code.ControllerDynamicNodeProvider, nvpMVC"/>
		</mvcSiteMapNode>
	</mvcSiteMapNode>
</mvcSiteMap>

Nice simple Layout for the SiteMap.  The site root doesn't use an area, just the Home Controller.

routes.MapRoute(
	"DefaultWithLanguage",
	"{languageCode}/{area}/{controller}",
	new { area="", controller="Home", action="Index"},
	new {languageCode = "^[a-zA-Z]{2,2}$"}			    
);

Default Route to handle the area and controller values only.  Default area is empty to keep root URLs clean.

public class AreaDynamicNodeProvider : NavigationNodeProvider
{
	public override IEnumerable<DynamicNode> GetDynamicNodeCollection()
	{
		foreach (var item in Repository.NodesAtDepth(1))
		{
			DynamicNode node = new DynamicNode(item.ID.ToString(), item.resource);
			node.ParentKey = "Root";
			node.Area = item.path;						
			yield return node;
		}
	}
}
public class ControllerDynamicNodeProvider : NavigationNodeProvider
{
	public override IEnumerable<DynamicNode> GetDynamicNodeCollection()
	{
		foreach (var item in Repository.NodesAtDepth(2))
		{
			DynamicNode node = new DynamicNode(item.ID.ToString(), item.resource);											
			node.ParentKey = item.parent_ID.ToString();				
			node.Controller = item.path;
			yield return node;
		}
	}
}

The DynamicNodeProviders are again very straight forward.  The Key and ParentKey values are valid and correct for the hierarchy.

My Navigation:

Home (localhost/en)
Area_1 (localhost/en/Area1)
Controller_A (localhost/en/{Should say Area1}Controller_A)
Controller_B (localhost/en/{Should say Area1}Controller_B)
Area_2 (localhost/en/Area2)
Controller_X (localhost/en/Controller_X)
Controller_Y (localhost/en/Controller_Y)

In all Controller navigation entries, there is no area in the URL.  My understanding of things says there should be one.
What am I missing?   

 

Mar 30, 2011 at 4:54 PM

Alright, I found one issue.  This is in 2.3.1 codebase.

DefaultSiteMapProvider.cs ->Line 1195 in method GetSiteMapNodeFromXmlElement()

// Get area, controller and action from node declaration
string area = node.GetAttributeValue("area");
string controller = node.GetAttributeValue("controller");

// Inherit area and controller from parent
var parentMvcNode = parentNode as MvcSiteMapNode;
if (parentMvcNode != null)
{
    if (area == null)
    {
        area = parentMvcNode.Area;
    }
    if (string.IsNullOrEmpty(controller))
    {
        controller = parentMvcNode.Controller;
    }
}
The area comparison vs the controller comparison.  An empty area is coming back as an empty string from node.GetAttributeValue("area"), not a null value. 
Basically, empty areas are not pulling from the parent, as the null check fails.
So matching up the comparison types should do it.  I'll roll the change into the 2.3.1 codebase and see what I get. 

Mar 31, 2011 at 8:37 PM

Ya, that was a bust.
I've been focusing on DefaultSiteMapProvider.AddDynamicNodesFor()

When the loop for the dynamic nodes runs, it clones an MVC node into a SiteMapNode, finds the location to insert it, and adds it.
I'm single stepping through and I see the following:

// Optionally, an area, controller and action can be specified. If so, override the clone.
if (!string.IsNullOrEmpty(dynamicNode.Area))
{
    clone.Area = dynamicNode.Area;
}
if (!string.IsNullOrEmpty(dynamicNode.Controller))
{
    clone.Controller = dynamicNode.Controller;
}
if (!string.IsNullOrEmpty(dynamicNode.Action))
{
    clone.Action = dynamicNode.Action;
}
 That all makes sense for values specified in the dynamic node provider, but I don't see any other time when those values would be updated.
Specifically, I thought there would be another clone or value copy in AddNode. 

Apr 4, 2011 at 6:55 PM

I guess this is quickly becoming my own scratchpad, but I'm not sure where else to put the stuff.

When I'm using the MvcSiteMap and DynamicNodeProviders I listed above, I'm starting to see some strange behaviour as the BuildSiteMap() runs.

The first Run of ProcessXmlNodes creates the Root of the Site and the first children correctly.  So before the 2nd recursion starts, my sitemap looks like this:

HOME
Area 1
Area 2
Area 3
Area 4

The HOME node is correctly marked as root and the 4 areas are listed as ChildNodes, all with correct URL's and Routes.

What I've noticed however, is that during the creation process, a 2nd "Home" node shows up.  From what I can tell, the 2nd "Home" node is a parent node for the AreaDynamicNodeProvider.  It gets created before
the AreaDynamicNodeProvider is fired, and seems to stick around in the stack.  It also has the same URL as the proper HOME node.  When the Area child nodes are created, and added to the SiteMap, this incorrect parent node is then passed down the recursion
as the parent node for the ControllerDynamicNodeProvider.  This ends up allocating the Controller Nodes with the wrong parent info, thus the Area values are not propagated to the children.

The worst of it all is that this rogue parent node has the same URL as the correct root node.  So when the controller children are being added to the SiteMap, duplicate URLs are being hit, and thus dropping nodes.
After the 2nd recursion of ProcessXmlNodes returns, my SiteMap becomes:

HOME
Area 1
	Controller 1
	Controller 2
Area 3
	Controller 3
	Controller 4

I've lost Area 2 and 4.  I'm trying to figure out why this extra "home" node is showing up, but not having much luck.  It seems the node is required to process the dynamic node providers properly.  Is that true?

Apr 5, 2011 at 7:10 AM

That's correct, the node is required. Did you have any luck pinning this down? If so, can you provide a patch or references to where this should be fixed?

Apr 5, 2011 at 12:56 PM

Thanks for that confirmation :)  I'll have to play around a bit to see how to get a patch in place.  My initial thoughts are leaning to either pulling data from the finalized SiteMap nodes while running through the ProcessXmlNodes, or playing around with
recursion order to see if a node with more information can be used instead of the dynamic placeholder.  Either way, the attempts are off in their own branch in my code, so I can get some solid tests in place.