MvcSiteMapProvider - multiple pages must be associated with one Node menu

In my MVC3 project , I installed Maartenba MvcSiteMapProvider v.3.2.1 , and I have a very simple, static, two-level menu that I created. Below is the structure of the concept map.

- Home - Member Center - Member Listing [SELECTED] - Event Calendar - Documents - Administration 

Now there are many subpages in the list of participants (for example, Detail, Edit, etc.), but I do not want them to appear as third-level menu items (mainly because they are associated with a specific participant identifier). However, I want all of these third-level pages to be “tied” to the node's list of members list so that it appears as selected on these pages.

I have the following code in the Mvc.SiteMap file:

 <mvcSiteMapNode title="Home" controller="Home" action="Index"> <mvcSiteMapNode title="Member Center" area="Members" controller="Home" action="Index" roles="Approved Member" > <mvcSiteMapNode title="Member Listing" area="Members" controller="Member" action="List" /> <mvcSiteMapNode title="Event Calendar" area="Members" controller="Event" action="List" /> <mvcSiteMapNode title="Documents" area="Members" controller="Document" action="List" /> </mvcSiteMapNode> <mvcSiteMapNode title="Administration" area="Admin" controller="Home" action="Index" roles="Site Administrator" > </mvcSiteMapNode> </mvcSiteMapNode> 

To display the menu, I use the following code in the _Layout.cshtml file:

 @Html.MvcSiteMap().Menu(1, true, true, 1, true, true) 

Finally, I modified the SiteMapNodeModel.cshtml file to add a class to the node that corresponds to the page the user is viewing. Here's a snippet that displays the node menu.

 @model SiteMapNodeModel <a href="@Model.Url" class="@(Model.IsCurrentNode ? "selectedMenuItem" : "")">@Model.Title</a> 

The display and map navigation work just fine until I move on to the members area. For example, if I pass Members/Member/List (which displays the menu correctly) and go to a page like Members/Member/Detail/1 , the child nodes in the Member Center ("Member List", "Event Calendar", etc. ) disappear . So here are my two problems that I have with my current code:

  • I want to indicate that any given page is part of the parent menu of the "Member Center" node, so that the nodes of the children’s menu "Member Center" will be displayed, regardless of whether this page is defined as a specific node in the menu structure.

  • I want to indicate (perhaps in a view or controller action) that a particular page should be bound to a specific node menu. For example, when the user is in Members/Member/Detail/1 , I just want the child "Member Listing" node to be listed as IsCurrentNode so that the SiteMapNodeModel.cshtml file correctly decorates it with the "selectedMenuItem" class.

Any suggestions?

+7
source share
3 answers

You can add third-level nodes to the XML sitemap and specify visibility to hide them from the menu. Here is the node declaration to display it only in breadcrumbs:

 <mvcSiteMapNode area="Members" controller="Member" action="Detail" visibility="SiteMapPathHelper,!*" title="Member details" /> 

Edit:

As far as I know, you cannot set IsCurrentNode. But you can check if the node menu is currently selected with the following code (I use it in the SiteMapNodeModel display template):

 IList<string> classes = new List<string> (); if (Model.IsCurrentNode || Model.IsInCurrentPath && !Model.Children.Any ()) { classes.Add ("menu-current"); } 
+6
source

Adding to Max. answer. I would also create an extension method for SiteMapNodeModel. What could you use to implement all the user logic needed for this:

 public static class SiteMapNodeModelExtender { public static bool IsRealCurrentNode(this SiteMapNodeModel node) { // Logic to determine the "real" current node... // A naive implementation could be: var currentPath = HttpContext.Current.Request.Url.AbsolutePath; return currentPath.StartsWith("Members/Member/") && node.Title.Equals("Member Center") } } 

and change the display template accordingly:

 /* Also check IsRealCurrentNode, depending on the use case maybe only IsRealCurrentNode */ @if ((Model.IsCurrentNode || Model.IsRealCurrentNode()) && Model.SourceMetadata["HtmlHelper"].ToString() != "MvcSiteMapProvider.Web.Html.MenuHelper") { <text>@Model.Title</text> } else if (Model.IsClickable) { <a href="@Model.Url ">@Model.Title</a> } else { <text>@Model.Title</text> } 
+1
source

In addition to Max Kiselyov’s answer, if you want to use this technique, but you can use attributes for your actions with the controller, I did the following:

Define a custom visibility provider:

 public class AlwaysInvisibleVisibilityProvider : ISiteMapNodeVisibilityProvider { public bool IsVisible(SiteMapNode node, HttpContext context, IDictionary<string, object> sourceMetadata) { return false; } } 

Then subclass MvcSiteMapNodeAttribute:

 public class InvisibleMvcSiteMapNodeAttribute : MvcSiteMapNodeAttribute { public InvisibleMvcSiteMapNodeAttribute(string key, string parentKey) { Key = key; ParentKey = parentKey; VisibilityProvider = typeof (AlwaysInvisibleVisibilityProvider).AssemblyQualifiedName; } } 

And you can use it in controller actions:

 [HttpGet] [InvisibleMvcSiteMapNodeAttribute("ThisNodeKey", "ParentNodeKey")] public ViewResult OrderTimeout() { return View("Timeout"); } 
+1
source

All Articles