I am using ASP.NET MVC 4, with mvcSiteMapProvider to manage my menus.
I have a custom menu builder that evaluates whether a node is on the current branch (ie, if the SiteMap.CurrentNode
is either the CurrentNode or the CurrentNode is nested under it). The code is included below, but essentially checks the url of each node and compares it with the url of the currentnode, up through the currentnodes "family tree".
The CurrentBranch is used by my custom menu builder to add a class that highlights menu items on the CurrentBranch.
My custom menu works fine, but I have found that the mvcSiteMapProvider
does not seem to evaluate the url
of the CurrentNode
in a consistent manner:
When two nodes point to the same action and are distinguished only by a parameter of the action, SiteMap.CurrentNode does not seem to use the correct route (it ignores the distinguishing parameter and defaults to the first route that that maps to the action defined in the node).
In an app I have Members
.
A Member has a MemberStatus
field that can be "Unprocessed", "Active" or "Inactive". To change the MemberStatus, I have a ProcessMemberController
in an Area called Admin. The processing is done using the Process
action on the ProcessMemberController
.
My mvcSiteMap has two nodes that BOTH map to the Process
action. The only difference between them is the alternate
parameter (such are my client's domain semantics), that in one case has a value of "Processed" and in the other "Unprocessed":
Nodes:
<mvcSiteMapNode title="Process" area="Admin" controller="ProcessMembers" action="Process" alternate="Unprocessed" />
<mvcSiteMapNode title="Change Status" area="Admin" controller="ProcessMembers" action="Process" alternate="Processed" />
Routes:
The corresponding routes to these two nodes are (again, the only thing that distinguishes them is the value of the alternate parameter):
context.MapRoute(
"Process_New_Members",
"Admin/Unprocessed/Process/{MemberId}",
new { controller = "ProcessMembers",
action = "Process",
alternate="Unprocessed",
MemberId = UrlParameter.Optional }
);
context.MapRoute(
"Change_Status_Old_Members",
"Admin/Members/Status/Change/{MemberId}",
new { controller = "ProcessMembers",
action = "Process",
alternate="Processed",
MemberId = UrlParameter.Optional }
);
The Html.ActionLink helper uses the routes and produces the urls I expect:
@Html.ActionLink("Process", MVC.Admin.ProcessMembers.Process(item.MemberId, "Unprocessed")
// Output (alternate="Unprocessed" and item.MemberId = 12): Admin/Unprocessed/Process/12
@Html.ActionLink("Status", MVC.Admin.ProcessMembers.Process(item.MemberId, "Processed")
// Output (alternate="Processed" and item.MemberId = 23): Admin/Members/Status/Change/23
In both cases the output is correct and as I expect.
Let's say my request involves the second option, ie, /Admin/Members/Status/Change/47
, corresponding to alternate = "Processed" and a MemberId of 47.
Debugging my static CurrentBranch property (see below), I find that SiteMap.CurrentNode shows:
PreviousSibling: null
Provider: {MvcSiteMapProvider.DefaultSiteMapProvider}
ReadOnly: false
ResourceKey: ""
Roles: Count = 0
RootNode: {Home}
Title: "Process"
Url: "/Admin/Unprocessed/Process/47"
Ie, for a request url of /Admin/Members/Status/Change/47, SiteMap.CurrentNode.Url
evaluates to /Admin/Unprocessed/Process/47
. Ie, it is ignorning the alternate
parameter and using the wrong route.
/// <summary>
/// ReadOnly. Gets the Branch of the Site Map that holds the SiteMap.CurrentNode
/// </summary>
public static List<SiteMapNode> CurrentBranch
{
get
{
List<SiteMapNode> currentBranch = null;
if (currentBranch == null)
{
SiteMapNode cn = SiteMap.CurrentNode;
SiteMapNode n = cn;
List<SiteMapNode> ln = new List<SiteMapNode>();
if (cn != null)
{
while (n != null && n.Url != SiteMap.RootNode.Url)
{
// I don't need to check for n.ParentNode == null
// because cn != null && n != SiteMap.RootNode
ln.Add(n);
n = n.ParentNode;
}
// the while loop excludes the root node, so add it here
// I could add n, that should now be equal to SiteMap.RootNode, but this is clearer
ln.Add(SiteMap.RootNode);
// The nodes were added in reverse order, from the CurrentNode up, so reverse them.
ln.Reverse();
}
currentBranch = ln;
}
return currentBranch;
}
}
What am I doing wrong?
The routes are interpreted by Html.ActionLlink as I expect, but are not evaluated by SiteMap.CurrentNode as I expect. In other words, in evaluating my routes, SiteMap.CurrentNode ignores the distinguishing alternate parameter.
I think it is because you are trying to obtain the route FROM the parameters. Basically, MVC is just trying to GUESS what route you could be referring to.
The correct way would be to handle routes by name. So the sitemap should reference to a Route name rather than the Controller, action, etc.