I have the following code in mvc.sitemap file (using mvcsitemapprovider mvc3):
<?xml version="1.0" encoding="utf-8" ?>
<mvcSiteMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0"
xsi:schemaLocation="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0 MvcSiteMapSchema.xsd">
<mvcSiteMapNode title="Home" controller="Home" action="Index" key="Home">
<mvcSiteMapNode title="Internal Mail Delivery Days - Display" controller="InternalMailCalendar" action="Index" key="InternalMailCalendar" />
<mvcSiteMapNode title="Internal Mail Delivery Days - Create" controller="InternalMailCalendar" action="Create" />
</mvcSiteMapNode>
</mvcSiteMap>
also i am calling this function to add the breadcrumb:
<MvcSiteMapNode(Title:="Edit", ParentKey:="InternalMailCalendar")> _
Function Edit(ByVal id As Integer) As ActionResult
If id > 0 Then
Dim obj = Mapper.Map(Of InternalMailCalendarViewModel)(_repositoryForIntMailCalendar.FindSingleByCondition(Function(x) x.ID = id))
If obj IsNot Nothing Then
Return View(obj)
End If
End If
Throw New HttpException(404, "Invalid Calendar ID")
End Function
but nothing gets displayed in the breadcrumb only for this dynamic url. any ideas please?
To use custom parameters (such as "id"), you must configure MvcSiteMapProvider to take them into consideration. There are 2 options - you can either configure 1 node per controller action (as you have done) or you can configure 1 node per route value combination (in your case 1 node per id).
There is a detailed article on this subject with downloadable demos called How to Make MvcSiteMapProvider Remember a User's Position. These 2 StackOverflow answers might also be helpful.
But here is the reader's digest version. For CRUD operations that are only available to logged-in users (that no search engine will ever see), the best way is to use preservedRouteParameters to force a single set of nodes to match every record.
First things first. Let's do a complete example with Index, Details, Create, Edit, and Delete just to outline where they would go should you use them. You can of course limit your implementation to the features you need and you can change this to using MvcSiteMapNodeAttribute if you like. But I am showing them here in XML so they are easier to picture in your mind. The first thing is to arrange the nodes appropriately so your breadcrumb trail will correctly show the current position.
<mvcSiteMapNode title="Home" controller="Home" action="Index" key="Home">
<mvcSiteMapNode title="Internal Mail Delivery Days" controller="InternalMailCalendar" action="Index" key="InternalMailCalendar">
<mvcSiteMapNode title="Create" controller="InternalMailCalendar" action="Create" visibility="SiteMapPathHelper,!*"/>
<mvcSiteMapNode title="Details" controller="InternalMailCalendar" action="Details" visibility="SiteMapPathHelper,!*" preservedRouteParameters="id">
<mvcSiteMapNode title="Edit" controller="InternalMailCalendar" action="Edit" visibility="SiteMapPathHelper,!*" preservedRouteParameters="id"/>
<mvcSiteMapNode title="Delete" controller="InternalMailCalendar" action="Delete" visibility="SiteMapPathHelper,!*" preservedRouteParameters="id"/>
</mvcSiteMapNode>
</mvcSiteMapNode>
</mvcSiteMapNode>
So navigating your nodes would look something like this in the breadcrumb trail.
That is what we are going for, but we are not there yet. With preservedRouteParameters="id" on our nodes, you can now see that they will match the current request when navigating the pages. Do note that you must provide navigation from record to record in your site's content when using preservedRouteParameters because the menu will not display the records. Usually this is done by displaying a grid of all of the records with links to the appropriate commands (Details, Edit, Delete, etc).
There are 2 additional things to take care of - setting the title of the node dynamically and hiding the nodes that pertain to specific records from the menu. Why? Because when using preservedRouteParameters, it is not possible to show a list of all of the records in MvcSiteMapProvider - you only get the current record. Therefore, the only thing we get is a breadcrumb trail. It is possible to list everything, but only if you add a node for each record to MvcSiteMapProvider and set the "id" for each record explicitly, but that is not what we are doing in this example.
Next, lets focus on the visibility. As you can see, I have already set up the filters in the visibility attribute as visibility="SiteMapPathHelper,!*"
. This will tell the FilteredSiteMapNodeVisibility provider to only show the node on the SiteMapPath HTML helper, but hide it everywhere else.
But there are 2 more things we need to get visibility working. We need to set the FilteredSiteMapNodeVisibilityProvider as the default and we need to turn off the Visibility Affects Descendants setting. If using internal DI, these can both be done in the web.config appSettings as follows.
<add key="MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider" value="MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider, MvcSiteMapProvider"/>
<add key="MvcSiteMapProvider_VisibilityAffectsDescendants" value="false"/>
If using external DI, you can configure these settings on the constructors of the SiteMapNodeVisibilityProviderStrategy and SiteMapBuilderSet, respectively.
// Visibility Providers
this.For<ISiteMapNodeVisibilityProviderStrategy>().Use<SiteMapNodeVisibilityProviderStrategy>()
.Ctor<string>("defaultProviderName").Is("MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider, MvcSiteMapProvider");
// Configure the builder sets
this.For<ISiteMapBuilderSetStrategy>().Use<SiteMapBuilderSetStrategy>()
.EnumerableOf<ISiteMapBuilderSet>().Contains(x =>
{
x.Type<SiteMapBuilderSet>()
.Ctor<string>("instanceName").Is("default")
.Ctor<bool>("securityTrimmingEnabled").Is(securityTrimmingEnabled)
.Ctor<bool>("enableLocalization").Is(enableLocalization)
.Ctor<bool>("visibilityAffectsDescendants").Is(false)
.Ctor<bool>("useTitleIfDescriptionNotProvided").Is(useTitleIfDescriptionNotProvided)
.Ctor<ISiteMapBuilder>().Is(builder)
.Ctor<ICacheDetails>().Is(cacheDetails);
});
Last but not least, we need to fix the title of the node so it changes each time we switch records. This will ensure we see the name or title of the record we are on in the breadcrumb instead of what is set in the title attribute of the XML.
The easiest way to accomplish this is with the SiteMapTitleAttribute. This attribute can just be added onto your action method.
<SiteMapTitle("Name", Target = AttributeTarget.ParentNode)> _
Function Edit(ByVal id As Integer) As ActionResult
If id > 0 Then
Dim obj = Mapper.Map(Of InternalMailCalendarViewModel)(_repositoryForIntMailCalendar.FindSingleByCondition(Function(x) x.ID = id))
If obj IsNot Nothing Then
Return View(obj)
End If
End If
Throw New HttpException(404, "Invalid Calendar ID")
End Function
This example assumes your model (the variable obj) has a property named "Name". The property can be named whatever you want, it just needs to match the configuration of the SiteMapTitle attribute. The second argument indicates that we want the parent node of the current node to change title. Remember, in our example above we want the Edit breadcrumb to look like this:
Home > Internal Mail Delivery Days > Record 1 > Edit
In other words, we don't want the title of the Edit node to change, we want the one that says "Record 1" to change to the name of the current record. Just be sure to leave the Target argument off of the Details node because on that node we need the current title to change dynamically, not the parent node.
Using preservedRouteParameters has a side-effect you need to be aware of - it is copying the "id" from the current request into all of the visible nodes that it is configured on. It is fine to reuse the route value "id" so long as it always means the same thing to all of the nodes that are visible. If you have another level of nodes you want to display at the same time that require a parameter, it must be named something different than the original parameter (in this case "id") so the values can be kept separate.
Also, you must ensure (manually) that all of the required values to build all of the visible URLs are available in the URL of each request, even if it means sticking extra information in the URL that your application doesn't require.