Vulnerability in EPiServer.Forms

Try our conversational search powered by Generative AI!

Eric Herlitz
Dec 10, 2012
  14296
(2 votes)

Writing a multi-level submenu using MVC in EPiServer 7

Using EPiServer 7 with MVC is a whole new way to use the framework and so many EPi developers seem to be frustrated by the fact that the commonly used episerver properties doens't work in the new version (the MVC-version that is, they work just fine if using EPi 7 with webforms).

This is not by pure evilness from EPi, it is by design and this is the way MVC works, no asp repeaters, no episerver menulist and so on, love it or leave it.

Building that submenu, aka the left menu

Some of the most common and essential parts of a website are the menus. CMS-developers are used to construct these from the nodes in the page tree so that’s what we will do.

Expected result

bild

Parts

We will need but a few parts

  • A view where we can display the menu
  • Some methods for the view
  • RecursiveMenu class
  • LeftMenu.cshtml partial view
  • Some css

A view where we can display the menu

This is a very simple view, in this particular case it doesn’t have a model but inherits from a System.Web.Mvc.WebViewPage page instead

@inherits MySite.Models.Pages.SubMasterPageViewModel
<div id="subMenuHeader">
    <h2>@SecondLevel.Name</h2>
</div>
<div id="subMenu">
    @Html.Partial("~/Views/Shared/Placeable/LeftMenu.cshtml", RightMenu(CurrentPage))
</div>

 

Some methods for the view

Since this view is a shared view in my original code it doesn’t have any model, the code below can be placed in any ViewModel or wherever you find it suitable.

using System.Collections.Generic;
using System.Linq;
using EPiServer;
using EPiServer.Core;
using EPiServer.Web.Routing;
using MySite.Business.Models; // Where we keep the RecursiveMenu     

namespace MySite.Models.Pages
{
    /// <summary>
    /// The main master page "model", it inherits from WebViewPage so we 
    /// can use the model regardless of which control that is loaded
    /// </summary>
    public abstract class SubMasterPageViewModel : System.Web.Mvc.WebViewPage
    {
        /// <summary>
        /// Retrieve the CurrentPage (since we don’t have a model)
        /// </summary>
        public PageData CurrentPage
        {
            get
            {
                PageRouteHelper pageRouteHelper = EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance<PageRouteHelper>();
                PageData currentPage = pageRouteHelper.Page;
                return currentPage;
            }
        }  
        /// <summary>
        /// Second level node
        /// </summary>
        public PageData SecondLevel
        {
            get
            {
                int pageStartId = EPiServer.Configuration.Settings.Instance.PageStartId;
                List<IContent> parents = DataFactory.Instance.GetAncestors(CurrentPage.PageLink).Where(x => x.ParentLink.ID >= pageStartId).Reverse().ToList();
                return parents.Any() ? parents.First() as PageData : CurrentPage;
            }
        }  
        /// <summary>
        /// Return all second level menu items
        /// </summary>
        /// <param name="currentPage"> </param>
        /// <returns></returns>
        public RecursiveMenu RightMenu(PageData currentPage)
        {
            // We feed this with the SecondLevel property since we want to start from the level below the top menu
            var menu = new RecursiveMenu(SecondLevel, CurrentPage);
            return menu;
        }
    }
}

 

The RecursiveMenu class

using EPiServer;
using EPiServer.Core;
using EPiServer.Filters;
using System.Collections.Generic;
using System.Linq;   

namespace MySite.Business.Models
{
    /// <summary>
    /// Builds a recursive menu object starting from the startPage parameter
    /// </summary>
    public class RecursiveMenu
    {
        /// <summary>
        /// Current node
        /// </summary>
        public PageData PageData { get; private set; }  
        /// <summary>
        /// Children of current node
        /// </summary>
        public List<RecursiveMenu> Children { get; private set; }  
        /// <summary>
        /// If this recursion is the current page
        /// </summary>
        public bool IsCurrentPage { get; private set; }  
        /// <summary>
        /// If the current PageData object is a parent to the current page loaded by currentPageGuid
        /// </summary>
        public bool IsInActiveChain { get; private set; }  
        /// <summary>
        /// Current depth
        /// </summary>
        public int MenuDepth { get { return DataFactory.Instance.GetAncestors(PageData.PageLink).Count(); } }  
        /// <summary>
        /// Default constructor, not very smart. Simply delivers ALL the FILTERED nodes from the starting page
        /// </summary>
        /// <param name="startPage"></param>
        /// <param name="currentPage"> </param>
        public RecursiveMenu(PageData startPage, PageData currentPage)
        {
            PageData = startPage;   if (currentPage != null)
            {
                // IsCurrentPage 
                IsCurrentPage = currentPage.PageGuid == startPage.PageGuid;  
                // IsInActiveChain
                // ...check in ancestors
                IsInActiveChain = DataFactory.Instance.GetAncestors(currentPage.PageLink).Any(x => (x as PageData).PageGuid == startPage.PageGuid) ||
                    // ...or if it's the current node
                    startPage == currentPage;
            }   PageDataCollection allChilds = DataFactory.Instance.GetChildren((startPage).PageLink);

            List<PageData> filteredChilds = FilterForVisitor.Filter(allChilds).Where(p => p.IsVisibleOnSite() && p.VisibleInMenu).ToList();   

            // If no children
            if (!filteredChilds.Any())
                return;  
                
            Children = new List<RecursiveMenu>();   

            // Only recurse into menu nodes that have children (disable to retrieve the entire tree)
            if (!IsInActiveChain)
                return;  

            foreach (PageData item in filteredChilds)
            {
                var menu = new RecursiveMenu(item, currentPage);   Children.Add(menu);
            }
        }
    }    
}

 

LeftMenu.cshtml

@using MySite.Business.Core
@using MySite.Business.Models
@model MySite.Business.Models.RecursiveMenu   @{
    if (Model.Children != null && Model.Children.Any() && Model.IsInActiveChain)
    {
        <ul>
        @foreach (RecursiveMenu item in Model.Children)
        {
            <li>
                <a href="@item.PageData.GetExternalUrl()" class="@(item.IsCurrentPage ? "active " : "")
                    @(item.Children != null ? "hasChildren " : "")
                    @(item.IsInActiveChain ? "bold " : "")
                    _@item.MenuDepth">
                    @item.PageData.Name   @if (item.Children != null)
                    {
                        <span> &raquo;</span>
                    }
                </a>
                @Html.Partial("~/Views/Shared/Placeable/LeftMenu.cshtml", item)
            </li>
        }
        </ul>
    }
}

 

 

The MySite.Business.Core contain a static extension method

/// <summary>
/// Retrieve the external url from a PageData object
/// </summary>
/// <param name="page"></param>
/// <returns></returns>
public static Uri GetExternalUrl(this PageData page)
{
    UrlBuilder url = new UrlBuilder(page.LinkURL);
    EPiServer.Global.UrlRewriteProvider.ConvertToExternal(url, page.PageLink, System.Text.Encoding.UTF8);   return url.Uri;
}

 

Some css

#subMenuHeader {
    background-color: #c50e1f;
    padding: 0 10px;
    margin: 0;
}   #subMenuHeader h2 {
        font-size: 1.0em;
        color: #fff;
        padding: 0;
        margin: 0;
    }   #subMenu ul {
    padding: 0;
    margin: 0;
    list-style: none;
}   #subMenu ul a {
        width: 100%;
        display: block;
        color: #000;
    }   #subMenu ul a.active {
            background-color: #ccc;
            font-weight: bold;
        }   #subMenu ul a.bold {
            font-weight: bold;
        }   #subMenu ul ul {
        padding: 0 0 0 10px;
        margin: 0;
        list-style: none;
    }

All done!

Dec 10, 2012

Comments

Dec 11, 2012 06:22 AM

Thanks for sharing this, its something alot of developers are asking for.

May 24, 2013 02:51 PM

Thanks for menus in EPi7. Great work!!!. I have implemented this menus but i am facing problem. I want to set this menu from one level down to start page. Can you please suggest something to make it work? I am newbie to EPiServer.

camilla nyberg
camilla nyberg Sep 17, 2013 10:32 AM

Just what I needed, thank you!

Jonathan Roberts
Jonathan Roberts Aug 7, 2015 10:01 AM

Do you have this for Episerver 8?

Please login to comment.
Latest blogs
Join the Work Smarter Webinar: Working with the Power of Configured Commerce (B2B) Customer Segmentation December 7th

Join this webinar and learn about customer segmentation – how to best utilize it, how to use personalization to differentiate segmentation and how...

Karen McDougall | Dec 1, 2023

Getting Started with Optimizely SaaS Core and Next.js Integration: Creating Content Pages

The blog post discusses the creation of additional page types with Next.js and Optimizely SaaS Core. It provides a step-by-step guide on how to...

Francisco Quintanilla | Dec 1, 2023 | Syndicated blog

Stop Managing Humans in Your CMS

Too many times, a content management system becomes a people management system. Meaning, an organization uses the CMS to manage all the information...

Deane Barker | Nov 30, 2023

A day in the life of an Optimizely Developer - Optimizely CMS 12: The advantages and considerations when exploring an upgrade

GRAHAM CARR - LEAD .NET DEVELOPER, 28 Nov 2023 In 2022, Optimizely released CMS 12 as part of its ongoing evolution of the platform to help provide...

Graham Carr | Nov 28, 2023

A day in the life of an Optimizely Developer - OptiUKNorth Meetup January 2024

It's time for another UK North Optimizely meet up! After the success of the last one, Ibrar Hussain (26) and Paul Gruffydd (Kin + Carta) will be...

Graham Carr | Nov 28, 2023

Publish content to Optimizely CMS using a custom GPT from OpenAI 🤖

Do you find the traditional editor interface complicated and cluttered? Would you like an editorial AI assistant you can chat with? You can!

Tomas Hensrud Gulla | Nov 28, 2023 | Syndicated blog