Internal Subnav with BlogEngine.NET
One of the great ASP.NET web apps I’ve started using is BlogEngine.NET, a fairly simple yet powerful CMS. While it handles basic CMS type functionality without problems, there are something which it doesn’t make particularly easy, such as displaying navigation trees.
This can be largely overcome by extending some of the controls which BlogEngine ships with, such as the post list. I changed this to display a list of sub pages for the current page. Once you set a page’s parent page, the subnav is rendered on the masterpage.
We just need one control on page.aspx and some code to bind it in the code-behind:
<blog:PageList runat="server" ID="pageList" />
private void ServePage(Guid id) { ... if(pg.Parent == Guid.Empty) { // root page pageList.RootPageID = id; } else { // sub page of root level page pageList.RootPageID = pg.Parent; }
Out of the box, the html for the tree is generated once which won’t work if it’s different for every page, so you’ll also need to modify PageList.cs in the ~/App_Code/Controls folder. I kept the caching and replaced it with a dictionary for each page to map between page ID and HTML for the page list.
static PageList() { BlogEngine.Core.Page.Saved += (sender, args) => PageList.ClearCachedHtml(); ClearCachedHtml(); } private static void ClearCachedHtml() { HttpContext.Current.Items.Remove("PageList"); } //We also need a few more properties: #region Properties private static Dictionary<Guid,string> HtmlDictionary { get { const string cacheKey = "pageListDict"; var htmlDict = HttpContext.Current.Cache[cacheKey] as Dictionary<Guid, string>; if (htmlDict == null) { htmlDict = new Dictionary<Guid, string>(); HttpContext.Current.Cache[cacheKey] = htmlDict; } return htmlDict; } } private static string GetPageListForPage(Guid pageId) { string html; if (!HtmlDictionary.TryGetValue(pageId, out html)) { html = BlogEngine.Core.Utils.RenderControl(BindPages(pageId)); HtmlDictionary.Add(pageId, html); } return html; } public Guid? RootPageID { get { return ViewState["RootPageID"] == null ? (Guid?) null : new Guid(Convert.ToString(ViewState["RootPageID"])); } set { ViewState["RootPageID"] = value; } } #endregion
The root page ID specifies the parent pages whose children to render.
The final step is some changes to the private methods which render the lists:
public override void RenderControl(HtmlTextWriter writer) { writer.Write(GetPageListForPage(RootPageID??Guid.Empty)); // writer.Write(Environment.NewLine); } private static HtmlGenericControl BindPages(Guid? pageId) { //HtmlGenericControl div = new HtmlGenericControl("div"); var div = new HtmlGenericControl("ul"); div.Attributes.Add("class","softwareList"); div.ID = "pagelist"; foreach (BlogEngine.Core.Page page in BlogEngine.Core.Page.Pages) { if (page.ShowInList && page.IsVisibleToPublic && ((pageId.HasValue && pageId.Value == page.Parent) || (!pageId.HasValue && page.Parent.ToString() == "00000000-0000-0000-0000-000000000000"))) { div.Controls.Add(BuildSubPages(div, page)); } } return div; } private static HtmlGenericControl BuildSubPages(Control parentElement, BlogEngine.Core.Page page) { List<BlogEngine.Core.Page> subPages = BlogEngine.Core.Page.Pages.Where(p => p.Parent == page.Id).ToList(); HtmlGenericControl li = new HtmlGenericControl("li"); HtmlAnchor anc = new HtmlAnchor(); anc.HRef = page.RelativeLink.ToString(); anc.InnerHtml = page.Title; anc.Title = page.Description; li.Controls.Add(anc); if (subPages.Any()) { HtmlGenericControl ul = new HtmlGenericControl("ul"); li.Controls.Add(ul); foreach (BlogEngine.Core.Page p in subPages) { HtmlGenericControl innerLi = new HtmlGenericControl("li"); ul.Controls.Add(BuildSubPages(innerLi, p)); } } parentElement.Controls.Add(li); return li; }
Though this should work for unlimited navigation levels, I’ve only tested it with two. Ideally, Umbraco is suited excellently to this due to it’s ability to use XSLT for jobs like this.


Comments
Leave a Comment