I am currently implementing breadcrumb in a MVC3 Razor application, using Maartenba's MVC Sitemap Provider. The problem occurs when I try to create dynamic nodes, for the blog posts. What I want to achieve is to display Home>Blog for the blog page and Home>Blog>BlogPostTitle for the post page, where BlogPostTitle is the title of the currently displayed post. What I actually get is: Home>Blog both for the blog page and for any post page.
To test the dynamic nodes generation, I also included on the _Layout a call to @Html.MvcSiteMap().Menu(false, true, true). The displayed menu confirms that the nodes are correctly generated:
- Home
- About
- Blog
- Article #1 title
- Article #2 title
- Contact
Still, the @Html.MvcSiteMap().SiteMapPath() displays Home>Blog both for the blog page and for the articles.
Here is the code:
Sitemap:
<mvcSiteMapNode title="Home" controller="Home" action="Index">
<mvcSiteMapNode title="About" controller="Home" action="About"/>
<mvcSiteMapNode title="Blog" controller="BlogPost" action="Index" key="News" >
<mvcSiteMapNode title="" controller="BlogPost" action="Details" dynamicNodeProvider ="MyApp.Helpers.BlogPostDynamicNodeProvider, myApp" />
</mvcSiteMapNode>
<mvcSiteMapNode title="Contact" controller="Home" action="Contact"/>
</mvcSiteMapNode>
Dynamic node provider:
public class BlogPostDynamicNodeProvider : DynamicNodeProviderBase
{
public BlogPostRepository _repository = new BlogPostRepository();
public List<BlogPost> articles = new List<BlogPost>();
public override IEnumerable<DynamicNode> GetDynamicNodeCollection()
{
articles = _repository.FindAllBlogPosts();
var returnValue = new List<DynamicNode>();
foreach (var article in articles)
{
DynamicNode node = new DynamicNode();
node.ParentKey = "News";
node.Title = article.PostTitle;
node.RouteValues.Add("id", article.Post_ID);
returnValue.Add(node);
}
return returnValue;
}
public override CacheDescription GetCacheDescription()
{
return new CacheDescription("BlogPostDynamicNodeProvider")
{
SlidingExpiration = TimeSpan.FromMinutes(1)
};
}
}
BlogPostCOntroller
public ViewResult Index(long? BlogPost_ID)
{
var BlogPostList = new PagedData<BlogPost>();
BlogPostList.Data = repository.FindAllBlogPostsPaged(1, 3, 1);
BlogPostList.NumberOfPages = Convert.ToInt32(Math.Ceiling((double)repository.NumberOfPosts(1) / 3));
BlogPostList.CurrentPage = 1;
BlogPost pBlogPost = new BlogPost();
pBlogPost.PostDate = DateTime.Now;
return View(new BlogPostPagedViewModel(pBlogPost, repository.FindAllBlogPosts()
new SelectList(ctgRepository.FindAllBlogCategories(), "Category_ID", "Category_Name")));
}
[SiteMapPreserveRouteData]
public ActionResult Details(long id)
{
BlogPost post = repository.FindABlogPostByID(id);
return PartialView("_Details", post);
}
_Layout
<div id="pagecontent">
@Html.MvcSiteMap().Menu(false, true, true) <!-- for test purpose only -->
@Html.MvcSiteMap().SiteMapPath()
@Html.Partial("_PagedList", Model.BlogPostList)
</div><!-- page blogcontent -->
Blog Index view
<div id="blogcontent">
@Html.Partial("_PagedList", Model.BlogPostList)
</div><!-- end blogcontent -->
Blog _PagedList partial view
@using (Ajax.BeginForm(new AjaxOptions { UpdateTargetId = "blogcontent", InsertionMode = InsertionMode.Replace }))
{
foreach (var item in Model.Data)
{
<h2 class="posttitle">
@Ajax.ActionLink(item.PostTitle, "Details", "BlogPost", new { id = item.Post_ID }, new AjaxOptions { UpdateTargetId = "blogcontent" }, null)
</h2>
}
}
Details view
@model MyApp.Models.BlogPost
@using (Ajax.BeginForm(new AjaxOptions { UpdateTargetId = "blogcontent", InsertionMode = InsertionMode.Replace }))
{
<div class="blogpost">
...
</div>
}
_ Edit _
I discovered that, if I remove @Html.MvcSiteMap().SiteMapPath()
from _Layout and place it instead in the blog Index view and in the Details view, the breadcrumb displays the current location correctly.
But that means I need to place it on every page from the project, which is in contradiction with the role of the _Layout shared view!
Where is the mistake in my code?