Something like this?
public class SitemapNode { public string Title { get; set; } public string Url { get; set; } public List<SitemapNode> Nodes { get; set; } }
public class FilterPublicAccess : PageFilterBase { private readonly IPrincipal _principal; public FilterPublicAccess() { _principal = new GenericPrincipal(new GenericIdentity("visitor"), new[] { "Everyone" }); } public override bool ShouldFilter(PageData page) { return !page.ACL.QueryDistinctAccess(_principal, AccessLevel.Read); } public override bool ShouldFilter(IContent content) { var page = content as PageData; if (page == null) { return false; } return ShouldFilter(page); } }
public class SitemapGenerator { private readonly IContentLoader _contentLoader; private readonly UrlResolver _urlResolver; public SitemapGenerator(IContentLoader contentLoader, UrlResolver urlResolver) { _contentLoader = contentLoader; _urlResolver = urlResolver; } public SitemapNode GetNodes(PageReference startPage) { var page = _contentLoader.Get<PageData>(startPage); var node = new SitemapNode { Title = page.Name, Url = GetExternalUrl(page.PageLink) }; GetChildNodes(page, node); return node; } private void GetChildNodes(PageData page, SitemapNode node) { var publicAccessFilter = new FilterPublicAccess(); var publishedFilter = new FilterPublished(); var children = _contentLoader.GetChildren<PageData>(page.ContentLink) .Where(x => // skip container pages x.HasTemplate() && // skip pages that have 'Display in navigation' unchecked x.VisibleInMenu && // skip pages that are not visible to everyone !publicAccessFilter.ShouldFilter(x) && // skip unpublished pages !publishedFilter.ShouldFilter(x)) .ToList(); if (children.Count == 0) return; node.Nodes = new List<SitemapNode>(children.Count); foreach (var child in children) { var sitemapNode = new SitemapNode { Title = child.Name, Url = GetExternalUrl(child.PageLink) }; GetChildNodes(child, sitemapNode); node.Nodes.Add(sitemapNode); } } private string GetExternalUrl(PageReference pageReference) { var internalUrl = _urlResolver.GetUrl(pageReference); var url = new UrlBuilder(internalUrl); EPiServer.Global.UrlRewriteProvider.ConvertToExternal(url, null, Encoding.UTF8); return url.ToString(); } }
In page controller, you can generate sitemap like this:
var siteMapGenerator = ServiceLocator.Current.GetInstance<SitemapGenerator>(); var node = siteMapGenerator.GetNodes(ContentReference.StartPage);
Result from Alloy:
Hope this helps :)
You can create a helper class like this:
public static class SitemapHelper { public static void GenerateSitemap(this HtmlHelper html, SitemapNode sitemap) { html.ViewContext.Writer.WriteLine("<ul>"); WriteNode(html, sitemap); html.ViewContext.Writer.WriteLine("</ul>"); } private static void WriteNode(this HtmlHelper html, SitemapNode node) { html.ViewContext.Writer.WriteLine("<li>"); html.ViewContext.Writer.WriteLine("<a href=\"{0}\">{1}</a>", node.Url, node.Title); if (node.Nodes != null && node.Nodes.Count > 0) { html.ViewContext.Writer.WriteLine("<ul>"); foreach (var childNode in node.Nodes) { WriteNode(html, childNode); } html.ViewContext.Writer.WriteLine("</ul>"); } html.ViewContext.Writer.WriteLine("</li>"); } }
I don't know how your viewmodel looks like, but you could have something like this in controller:
var viewModel = new MyViewModel(currentPage); var siteMapGenerator = ServiceLocator.Current.GetInstance<SitemapGenerator>(); var sitemap = siteMapGenerator.GetNodes(ContentReference.StartPage); viewModel.Sitemap = sitemap; return View(viewModel);
View:
@model MyViewModel @{ Html.GenerateSitemap(Model.Sitemap); }
It should generate the following HTML for Alloy:
<ul> <li> <a href="/">Start</a> <ul> <li> <a href="/alloy-plan/">Alloy Plan</a> <ul> <li> <a href="/alloy-plan/download-alloy-plan/">Download Alloy Plan</a> </li> </ul> </li> <li> <a href="/alloy-track/">Alloy Track</a> <ul> <li> <a href="/alloy-track/download-alloy-track/">Download Alloy Track</a> </li> <li> <a href="/alloy-track/download-whitepaper-alloy-track/">Whitepaper</a> </li> </ul> </li> <li> <a href="/alloy-meet/">Alloy Meet</a> <ul> <li> <a href="/alloy-meet/download-alloy-meet/">Download Alloy Meet</a> </li> </ul> </li> <li> <a href="/about-us/">About us</a> <ul> <li> <a href="/about-us/news-events/">News & Events</a> <ul> <li> <a href="/about-us/news-events/events/">Events</a> <ul> <li> <a href="/about-us/news-events/events/reporting-made-simple/">Reporting Made Simple</a> </li> <li> <a href="/about-us/news-events/events/collaboration-made-simple/">Collaboration Made Simple</a> </li> <li> <a href="/about-us/news-events/events/risk-management-in-complex-projects/">Risk Management</a> </li> </ul> </li> <li> <a href="/about-us/news-events/press-releases/">Press Releases</a> <ul> <li> <a href="/about-us/news-events/press-releases/alloy-pays-it-forward/">Alloy Pays it Forward</a> </li> <li> <a href="/about-us/news-events/press-releases/newworld-wildlife-fund-chooses-alloy/">Alloy Saves Bears</a> </li> <li> <a href="/about-us/news-events/press-releases/alloy-plan-enhances-risk-management/">Enhances Risk Management</a> </li> <li> <a href="/about-us/news-events/press-releases/alloy-meet-acclaimed-for-top-collaboration-technology/">Top Collaboration Technology</a> </li> </ul> </li> </ul> </li> <li> <a href="/about-us/management/">Management</a> </li> <li> <a href="/about-us/contact-us/">Contact us</a> </li> <li> <a href="/about-us/become-a-reseller/">Become a reseller</a> </li> </ul> </li> </ul> </li> </ul>
HI,
Can I ask what your viewmodel looks like please? It's always tricky looking at someone elses code and your thoughts are all blank :).
Hi,
Let's say you have sitemap page type defined like this:
[ContentType(GUID = "d6030985-2cb1-4c03-801e-a282a8122bd0")] public class SitemapPage : PageData { // ... }
If you want to use PageViewModel<T> approach from Alloy:
public class MyViewModel : PageViewModel<StartPage> { public MyViewModel(SitemapPage currentPage) : base(currentPage) { } public SitemapNode Sitemap { get; set; } }
Otherwise you can define your viewmodel like this:
public class MyViewModel { public MyViewModel(SitemapPage currentPage) { CurrentPage = currentPage; } public SitemapPage CurrentPage { get; private set; } public SitemapNode Sitemap { get; set; } }
Page type:
[ContentType(GUID = "d6030985-2cb1-4c03-801e-a282a8122bd0")] public class SitemapPage : PageData { // ... }
View model:
public class SitemapViewModel { public SitemapViewModel(SitemapPage currentPage) { CurrentPage = currentPage; } public SitemapPage CurrentPage { get; private set; } public SitemapNode Sitemap { get; set; } }
Controller:
public class SitemapPageController : PageController<SitemapPage> { public ActionResult Index(SitemapPage currentPage) { var viewModel = new SitemapViewModel(currentPage); var siteMapGenerator = ServiceLocator.Current.GetInstance<SitemapGenerator>(); var sitemap = siteMapGenerator.GetNodes(ContentReference.StartPage); viewModel.Sitemap = sitemap; return View(viewModel); } }
View (you may need to change the namespace):
@model SitemapDemo.Controllers.SitemapViewModel @{ Html.GenerateSitemap(Model.Sitemap); }
Hi, Many thanks for your patience - this is exactly what I needed and works like a charm - thansk for all your help,
:)
Jon
Hi,
Is it possible to generate a normal Sitemap for a site - not an XML site map but a navigation type site map that shows all the published pages on a site.
I can do it as a webform but for this project we are using MVC.
Thanks
Jon