Blog posts by Patrik Fomin2020-09-02T15:30:47.0000000Z/blogs/patrik-fomin/Optimizely WorldCreate a Signage type block/blogs/patrik-fomin/dates/2020/9/create-a-signage-type-block/2020-09-02T15:30:47.0000000Z<p>In this tutorial I will create a signage type of block. The idea is to have a block where the user can make an interactive decision that will progress the user to different content without re-loading the page. </p>
<p>Below are the end result from a new Foundation site using the signage block.</p>
<p><img src="https://www.david-tec.com/global/images/signage-block.gif" width="918" alt="" height="857" /></p>
<p>A few limitations.</p>
<ul>
<li>Doesn't currently work with personalization. Meaning, the content that is displayed along the path does not consider personalization. (The main block does.)</li>
<li>Currently doesn't work with forms. This is because of a javascript error.</li>
<li>Limited to only one signage block per page.</li>
</ul>
<p>Now lets get started.</p>
<h2>Overview</h2>
<p>The process will be, user clicks on a signage surface this in turn triggers a call to the backend that renders a new view that is then presented to the user. In this new view, a new signage can be present that allows for further interactions and so on. Meaning, we will not be bound to just one decision but can actually continue to build more and more depth (if we so wish.)</p>
<p><img src="/link/d30d6937b4e042ef87ff34118bbe642d.aspx" /></p>
<p>Here is an overview of the files that will I will create in this tutorial.</p>
<p><img src="/link/fafe0f64720b4918ada847ee378b00fd.aspx" /></p>
<h2>Step 1 - Blocks, controllers and views</h2>
<h3>Blocks</h3>
<p>First we create the 2 blocks that are needed. The first one is a container block will be used to display all options. The second one is an options block which will be created by an editor when an option is needed. To make it more flexible (thank you <a href="/link/ed6b0363772a4cca99cc870450f9078a.aspx">David Knipe</a> for this idea) we will use a content area that represents the clickable surface, then we have a second content area that will represent the new view to render. The idea is simple, the container block loads all options and all options decide themselves how they will render within the container area. Then once clicked, a different rendering will be used to display the new view to the user.</p>
<p>In the example below I will use controllers to make it simple to further enhance this, however it is possible to do it without controllers and instead use a <span>IViewTemplateModelRegistrator.</span></p>
<p><strong>SignageContainerBlock.cs</strong></p>
<p><code>[ContentType(DisplayName = "Signage container block",</code><br /><code>GUID = "af597729-f5c5-407e-85a9-a36652f4e419",</code><br /><code>Description = "",</code><br /><code>GroupName = GroupNames.Content)]</code><br /><code>[ImageUrl("~/assets/icons/cms/blocks/CMS-icon-block-23.png")]</code><br /><code>public class SignageContainerBlock : FoundationBlockData</code><br /><code>{</code><br /><code> [Display(Name = "Is this the first signage block on the page?", GroupName = SystemTabNames.Content, Order = 10)]</code><br /><code> public virtual bool IsRootProgressBlockContainer { get; set; }</code></p>
<p><code> [CultureSpecific]</code><br /><code> [Display(Name = "Progress choices", GroupName = SystemTabNames.Content, Order = 20)]</code><br /><code> [AllowedTypes(typeof(Blocks.SignageBlock.SignageBlock))]</code><br /><code> public virtual ContentArea ChoiceArea { get; set; }</code><br /><code>}</code></p>
<p><strong>SignageBlock.cs</strong></p>
<p><code>[ContentType(DisplayName = "Signage Block",</code><br /><code>GUID = "ce7f2aa7-8356-4511-a1f2-0d78355cfde9",</code><br /><code>Description = "",</code><br /><code>GroupName = GroupNames.Content)]</code><br /><code>[ImageUrl("~/assets/icons/cms/blocks/CMS-icon-block-23.png")]</code><br /><code>public class SignageBlock : FoundationBlockData</code><br /><code>{</code><br /><code> [CultureSpecific]</code><br /><code> [Display(Name = "Clickable area", GroupName = SystemTabNames.Content, Order = 10)]</code><br /><code> public virtual ContentArea ClickableArea { get; set; }</code></p>
<p><code> [Display(Name = "Clickable area CSS class", GroupName = SystemTabNames.Content, Order = 20)]</code><br /><code> public virtual string ClickableAreaCssClass { get; set; }</code></p>
<p><code> [CultureSpecific]</code><br /><code> [Display(Name = "Main content area", GroupName = SystemTabNames.Content, Order = 30)]</code><br /><code> public virtual ContentArea MainContentArea { get; set; }</code></p>
<p><code> [Display(Name = "Main content area CSS class", GroupName = SystemTabNames.Content, Order = 40)]</code><br /><code> public virtual string MainContentAreaCssClass { get; set; }</code><br /><code>}</code></p>
<p>This is pretty straight forward. I create a container block with 2 properties, the first one will let me know if we have written a HTML-container on the page already (this allows us to create multiple nested containers) and the second is a content area with options.</p>
<p>The second block is equally as straight forward, first the clickable area which can be anything followed by the main area which we render if someone click on the clickable area. The css properties are just if we want to style it differently.</p>
<h3>Controllers</h3>
<p>The controllers doesn't contain any particular information except for the TemplateDescriptor. I added them in this example to make it easy to add custom logic to the page.</p>
<p>There are a total of three controllers, two controllers for the signage block (one to render the choice and one to render the new view) and one controller for the container block.</p>
<p><strong>SignageContainerBlockController.cs</strong></p>
<p><code>[TemplateDescriptor(Default = true)]</code><br /><code>public class SignageContainerBlockController : BlockController<SignageContainerBlock></code><br /><code>{</code><br /><code> public override ActionResult Index(SignageContainerBlock currentBlock)</code><br /><code> {</code><br /><code> // Do some custom stuff here</code></p>
<p><code> var viewModel = new BlockViewModel<SignageContainerBlock>(currentBlock);</code><br /><code> return PartialView("~/Features/Blocks/SignageBlock/SignageContainerBlock.cshtml", viewModel);</code><br /><code> }</code><br /><code>}</code></p>
<p><strong>SignageBlockController.cs</strong></p>
<p><code>[TemplateDescriptor(Default = true)]</code><br /><code>public class SignageBlockController : BlockController<SignageBlock></code><br /><code>{</code><br /><code> public override ActionResult Index(SignageBlock currentBlock)</code><br /><code> {</code><br /><code> // Do some custom stuff here</code></p>
<p><code> var viewModel = new BlockViewModel<SignageBlock>(currentBlock);</code><br /><code> return PartialView("~/Features/Blocks/SignageBlock/SignageBlock.cshtml", viewModel);</code><br /><code> }</code><br /><code>}</code></p>
<p><strong>SignageBlockChoiceAreaController.cs</strong></p>
<p><code>[TemplateDescriptor(Tags = new [] { "SignageContent" }, TemplateTypeCategory = TemplateTypeCategories.MvcPartialController, Inherited = true, AvailableWithoutTag = false)]</code><br /><code>public class SignageBlockChoiceAreaController : PartialContentController<SignageBlock></code><br /><code>{</code><br /><code> public override ActionResult Index(SignageBlock currentBlock)</code><br /><code> {</code><br /><code> // Do some custom stuff here</code></p>
<p><code> var viewModel = new BlockViewModel<SignageBlock>(currentBlock);</code><br /><code> return PartialView("~/Features/Blocks/SignageBlock/SignageBlockChoiceArea.cshtml", viewModel);</code><br /><code> }</code><br /><code>}</code></p>
<p>The only interesting thing in the controllers is the TemplateDescriptor in the SignageBlockChoiceAreaController. In here we define a Tag that we will be referencing in the container view in order to render the choice area and not the new view.</p>
<h3>Views</h3>
<p>We will also create three views, one for each controller.</p>
<p><strong>SignageContainerBlock.cshtml</strong></p>
<p><code>@model IBlockViewModel<Foundation.Features.Blocks.SignageBlock.SignageContainerBlock></code></p>
<p><code>@helper PrintItems()</code><br /><code>{</code><br /><code> if (Model.CurrentBlock.ChoiceArea != null)</code><br /><code> {</code><br /><code> @Html.PropertyFor(m => Model.CurrentBlock.ChoiceArea, new { Tag = "SignageContent" })</code><br /><code> }</code><br /><code>}</code></p>
<p><code>@if (Model.CurrentBlock.IsRootProgressBlockContainer)</code><br /><code>{</code><br /><code> <div id="signage-block" style="background-color: @Model.CurrentBlock.BackgroundColor; opacity:@Model.CurrentBlock.BlockOpacity;" class="@(Model.CurrentBlock.Padding + " " + Model.CurrentBlock.Margin)"></code><br /><code> @PrintItems()</code><br /><code> </div></code><br /><code>}</code><br /><code>else</code><br /><code>{</code><br /><code> @PrintItems() </code><br /><code>}</code></p>
<p><strong>SignageBlock.cshtml</strong></p>
<p><code>@model IBlockViewModel<Foundation.Features.Blocks.SignageBlock.SignageBlock></code></p>
<p><code><div style="background-color: @Model.CurrentBlock.BackgroundColor; opacity:@Model.CurrentBlock.BlockOpacity;" class="@(Model.CurrentBlock.Padding + " " + Model.CurrentBlock.Margin + " " + Model.CurrentBlock.MainContentAreaCssClass)"></code><br /><code> @Html.PropertyFor(m => m.CurrentBlock.MainContentArea)</code><br /><code></div></code></p>
<p><strong>SignageBlockChoiceArea.cshtml</strong></p>
<p><code>@model IBlockViewModel<Foundation.Features.Blocks.SignageBlock.SignageBlock></code></p>
<p><code>@{var id = ((IContent)Model.CurrentBlock).ContentLink.ID;}</code><br /><code><div style="background-color: @Model.CurrentBlock.BackgroundColor; opacity: @Model.CurrentBlock.BlockOpacity; cursor: pointer;" data-id="@id" class="signage-block-link @(Model.CurrentBlock.Padding + " " + Model.CurrentBlock.Margin + " " + Model.CurrentBlock.ClickableAreaCssClass)"></code><br /><code> @Html.PropertyFor(m => Model.CurrentBlock.ClickableArea)</code><br /><code></div></code></p>
<p>We basically just print out the content areas and not much more. The container block check if we have the property IsRootProgressBlockContainer set, if not then we first print out the container followed by the choices otherwise we only print out the choices.</p>
<p>The SignageBlockChoiceArea gets the ID-number of the block and assigns it to a data-id property. That way we can load the correct choice block in the API.</p>
<h2>Step 2 - API and helpers</h2>
<p>Now that we have all blocks, controllers and views in place it is time to look at the API that is used to render the "new view" and return it to the user. This process is pretty straight forward. </p>
<p><strong>SignageBlockApiController.cs</strong></p>
<p><code>public class SignageBlockApiController : Controller</code><br /><code>{</code><br /><code> [HttpGet]</code><br /><code> [Route("block/progress")]</code><br /><code> public ActionResult GetBlockProgression(int id)</code><br /><code> {</code><br /><code> var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();</code><br /><code> var componentContentReference = new ContentReference(id);</code><br /><code> contentLoader.TryGet(componentContentReference, out SignageBlock signageBlock);</code></p>
<p><code> if (signageBlock == null)</code><br /><code> return Content("");</code></p>
<p><code> var viewModel = new BlockViewModel<SignageBlock>(signageBlock);</code><br /><code> var viewResult = ControllerContext.RenderPartialToString("~/Features/Blocks/SignageBlock/SignageBlock.cshtml", viewModel);</code><br /><code> return Content(viewResult, "text/html");</code><br /><code> }</code><br /><code>}</code></p>
<p>First we load the block (or return empty if the block can't be found) then we call the helper method to render the contents into a string and finally we return it as HTML to the user.</p>
<p><strong>SignageBlockHelper.cs</strong></p>
<p><code>public static class SignageBlockHelper</code><br /><code>{</code><br /><code> public static string RenderPartialToString(this ControllerContext context, string partialViewName, object model)</code><br /><code> {</code><br /><code> return RenderPartialToStringMethod(context, partialViewName, model);</code><br /><code> }</code></p>
<p><code> public static string RenderPartialToStringMethod(ControllerContext context, string partialViewName, object model)</code><br /><code> {</code><br /><code> ViewDataDictionary viewData = new ViewDataDictionary(model);</code><br /><code> TempDataDictionary tempData = new TempDataDictionary();</code><br /><code> return RenderPartialToStringMethod(context, partialViewName, viewData, tempData);</code><br /><code> }</code></p>
<p><code> public static string RenderPartialToStringMethod(ControllerContext context, string partialViewName, ViewDataDictionary viewData, TempDataDictionary tempData)</code><br /><code> {</code><br /><code> ViewEngineResult result = ViewEngines.Engines.FindPartialView(context, partialViewName);</code></p>
<p><code> if (result.View == null)</code><br /><code> return String.Empty;</code></p>
<p><code> StringBuilder sb = new StringBuilder();</code><br /><code> using (StringWriter sw = new StringWriter(sb))</code><br /><code> {</code><br /><code> using (HtmlTextWriter output = new HtmlTextWriter(sw))</code><br /><code> {</code><br /><code> ViewContext viewContext = new ViewContext(context, result.View, viewData, tempData, output);</code><br /><code> result.View.Render(viewContext, output);</code><br /><code> }</code><br /><code> }</code></p>
<p><code> return sb.ToString();</code><br /><code> }</code><br /><code>}</code></p>
<p>There is no black magic happening. We simply take a controller context along with the view and model which we then render using one of the ViewEngines registered. </p>
<h2>Step 3 - Javascript </h2>
<p>Now all that is needed is some javascript and we are done. The code below is using Webpack as I am using Foundation to build this. You can of course take out and just use the jQuery parts as you see fit. (I will highlight the changes needed to run this for Foundation.)</p>
<p><strong>signage-block.js</strong></p>
<p><code>import * as axios from "axios";</code></p>
<p><code>export default class SignageBlock {</code><br /><code> init() {</code><br /><code> let oldBlockHtml = '';</code><br /><code> $('#signage-block').on('click', '.signage-block-link', (e) => {</code><br /><code> oldBlockHtml = $('#signage-block').html();</code><br /><code> let id = $(e.currentTarget).data('id');</code></p>
<p><code> $('#signage-block').html('<img src="/Assets/imgs/spinner.gif" style="width: 100px; height: 100px;" />');</code><br /><br /><code> axios.get('/block/progress?id=' + id)</code><br /><code> .then((result) => {</code><br /><code> $('#signage-block').html(result.data);</code><br /><code> }).catch((error) => {</code><br /><code> $('#signage-block').html(oldBlockHtml);</code><br /><code> notification.error(error);</code><br /><code> });</code><br /><code> });</code><br /><code> }</code><br /><code>}</code></p>
<p>First we create a Webpack class which contains an init method. (This is an optional and only included because I write this for Foundation.) Inside the Init method we have a jQuery event handler that binds the 'click' on all objects that has the class 'signage-block-link' which can be found in the SignageBlockChoiceArea.cshtml file. Once such a class is detected, we bind a click event to it. This fetches the ID-number and makes a call to the API. The spinner is of course optional and just there to show that something is happening. Once the data has been loaded we display it. In case we fail to load, we put the old data back into the container.</p>
<h3>Register in Webpack (For Foundation)</h3>
<p>In order to get it to work in Foundation you will need to register it in Webpack. </p>
<p>Open <em>Assets > js > common > foundations.cms.js</em></p>
<p>Add a new import (make sure the path is correct)</p>
<p><code>import SignageBlock from "Features/Blocks/SignageBlock/signage-block";</code></p>
<p>Then call the init method at the bottom where all the init calls are happening.</p>
<p><code>let signageBlock = new SignageBlock();</code><br /><code>signageBlock.init();</code></p>
<h2>Wrapup</h2>
<p>This is it. This is all that is needed to create your own signage block. You can then put in tracker inside the API to follow what users are doing with the block. </p>
<h3>API failure</h3>
<p>In case you encounter a problem with the API and that you can not make any calls, then it is most likely because you haven't registered the use of MVC route based routing in the Global.asax class.</p>
<p>Open Global.asax or the class where you register your routes and then add the following routes.MapMvcAttributeRoutes();</p>
<p><img src="/link/e732314f0a2c41a583a8d8fef96a2845.aspx" /></p>[Tutorial] Creating your own menu inside the Episerver UI using MVC/blogs/patrik-fomin/dates/2020/7/creating-your-own-administrator-menu-inside-episerver/2020-07-31T13:04:30.0000000Z<p>A few weeks ago we started a project to create a new solution for the Episerver Education store (more on this in upcoming blog posts) and one of things we had to do was to create a new administrative system to handle this. In order to make it easily acceissble for everyone working for the Education department at Episerver we decided to add links in the menu to manage everything. Sounds simple? Well yes and no, tag along and I will explore some of the problems I encountered along the way.</p>
<p>(If you want to read more you can visit Episerver world <a href="/link/1b13247ab7954914b2484a0a48d44d29.aspx">page</a> about menu items or visit my <a href="/link/9fbdf19272cd4d72b5f5fd43fb19dcf0.aspx">blog post</a> about dynamic MVC menu routes for an example code and my aha moment.)</p>
<p>Here is what are we trying to achieve. Our own custom Education store menu with some links to various functions. In this tutorial I will go through all steps needed to create the menu below.</p>
<h2>Menu items</h2>
<p><img src="/link/eb5e09208d234db6a93e89f7b314ca48.aspx" /></p>
<p>The menu consists of 3 elements:</p>
<ol>
<li>The name Edu. platform is what's called the "product name". This is (besides being listed above) also what you will see in the waffle menu (9 dot menu to the left).</li>
<li>Next to the product name is menu level one.</li>
<li>And finally below menu level one is (lo and behold) menu level two. <em>Tip: If menu level two does not have any menu items, it will not be shown.</em></li>
</ol>
<table>
<tbody>
<tr>
<td><strong>Item path</strong></td>
<td><strong>Name</strong></td>
<td><strong>Parent</strong></td>
</tr>
<tr>
<td>/education</td>
<td>Edu. platform</td>
<td>-</td>
</tr>
<tr>
<td>/education/sessions</td>
<td>Sessions</td>
<td>/education</td>
</tr>
<tr>
<td>/education/sessions/active</td>
<td>Active</td>
<td>/education/sessions</td>
</tr>
</tbody>
</table>
<p><strong>Why is this important to know?</strong></p>
<p>Well the menu is built on a child-parent relationship. If you have a menu item but no relation to the parent, it will not be display and when I say it will not be displayed, I mean it. This is what happens when one item is missing in the chain.<br /><img src="/link/b48bb530dbb045ae894b86fc2cd883db.aspx" /></p>
<p>So when you are building your own menu, you need to keep in mind that you always need to have a parent for your menu items except for the product name item (which uses the /global as parent.)</p>
<h2>MVC</h2>
<h3>Controller</h3>
<p>Next up we need to create the a MVC controller for the URL. Once the method Active is called, we want to show the correct menu item. The code is standard .NET MVC and includes no EpiServer elements. (I have included a RedirectRoute. More about why later on.)</p>
<p>Controller class that exposes one method with the route {mysite}/education/sessions/active.</p>
<p><code>using System.Web.Mvc;</code><br /><code>namespace Episerver.Sessions</code><br /><code>{</code><br /><code> [Authorize(Roles = "WebAdmins")]</code><br /><code> [RoutePrefix("education/sessions")]</code><br /><code> public class EducationSessionController : Controller</code><br /><code> {</code><br /><code> [Route(""), HttpGet]</code><br /><code> public ActionResult Active()</code><br /><code> {</code><br /><code> return RedirectToAction("Active");</code><br /><code> }</code></p>
<p><code> [Route("active"), HttpGet]</code><br /><code> public ActionResult Active()</code><br /><code> {</code><br /><code> return View();</code><br /><code> }</code><br /><code> }</code><br /><code>}</code></p>
<h3>View</h3>
<p>A blank cshtml page with one line to call <code>@Html.Raw(Html.CreatePlatformNavigationMenu())</code></p>
<p>You can of course add whatever you want onto this view as long as you add the above line.</p>
<h2>Menu Provider</h2>
<p>Now that we have the controller and class ready, we can begin to construct the provider that will render the menu for us.</p>
<p>Start with a new class, add the attribute [MenuProvider] and implement the interface IMenuProvider.</p>
<p><code>using System.Collections.Generic;</code><br /><code>using EPiServer.Shell.Navigation;</code></p>
<p><code>namespace Episerver.Sessions</code><br /><code>{</code><br /><code> [MenuProvider]</code><br /><code> public class MenuProvider : IMenuProvider</code><br /><code> {</code><br /><code> public IEnumerable<MenuItem> GetMenuItems()</code><br /><code> {</code><br /><code> // Menu comes here</code><br /><code> }</code><br /><code> }</code><br /><code>}</code></p>
<p><strong>Menu Items</strong> </p>
<p>Before we create the menu I want to quickly go through the Menu Item class. Below we will use the UrlMenuItem which extends the MenuItem class by allowing us to define a url in the constructor at the same time as the text and path. No other difference exists between them.</p>
<p>A menu item consists of several elements but I will only go into a few important ones here.</p>
<table>
<tbody>
<tr>
<td><strong>Name</strong></td>
<td><strong>Description</strong></td>
</tr>
<tr>
<td>Text</td>
<td>Controls what text that should be shown in the menu.</td>
</tr>
<tr>
<td>Url</td>
<td>The url that is matched to display the correct item.</td>
</tr>
<tr>
<td>Path</td>
<td>The Epi UI menu path where it should be displayed.</td>
</tr>
<tr>
<td>IsAvailable</td>
<td>If the current user should see the item or not.</td>
</tr>
</tbody>
</table>
<p><strong>Menu Sections</strong></p>
<p>A <code>SectionMenuItem</code> is an extended MenuItem with some smaller css fixes for the older rendering of the menu. However, this was more part of the legacy rendering and you do not need this to implement a menu.</p>
<h3>Adding menu items</h3>
<p>Now it is time to setup the items that should represent the structure. In our example we only have one method so technically it makes sense to only show the menu at level one. However, for the sake of this tutorial we will also do level two (like the picture shown above).</p>
<p>We start by adding a string to contain the base url followed by the product name. Add the following inside the class.</p>
<p><code>private readonly string PlatformPath = MenuPaths.Global + "/education";</code><br /><code>public IEnumerable<MenuItem> GetMenuItems()</code><br /><code>{</code><br /><code> var items = new List<MenuItem>();</code></p>
<p><code> items.Add(new UrlMenuItem("Edu. platform", PlatformPath</code><code>,</code><code> "")</code><br /><code> {</code><br /><code> SortIndex = 10,</code><br /><code> IsAvailable = (_) => true</code><br /><code> });</code></p>
<p><code> // More items to be added here</code></p>
<p><code> return items;</code><br /><code>}</code></p>
<p>This will create the product name that is shown in the waffle menu. It has no url attached to it as it is part of the global menu.</p>
<p>The next steps are the sessions (level one and two) menu items. Remember that in order to achieve the structure above /education/sessions/active we need to create 3 items.</p>
<p>Add the following two items.</p>
<p><code>items.Add(new UrlMenuItem("Sessions", EducationPlatform + "/sessions", "/education/sessions")</code><br /><code>{</code><br /><code> SortIndex = 20,</code><br /><code> IsAvailable = (_) => true</code><br /><code>});</code></p>
<p><code>items.Add(new UrlMenuItem("Active", EducationPlatform + "/sessions/active", "/education/sessions/active")</code><br /><code>{</code><br /><code> SortIndex = 30,</code><br /><code> IsAvailable = (_) => true</code><br /><code>});</code></p>
<p>As you might have noticed from the above, the first parameter is the Text followed by the Path and lastly the Url. Now if you were to run the project you would see your own menu link show up in the waffle menu and if you click on it, it should show 1 menu level one item called Sessions and 1 menu level two item called Active.</p>
<p><em>Note: The Sessions item can also be created with a SectionMenuItem instead of a UrlMenuItem. The end result should be the same.</em></p>
<p><strong>Child-Parent relationship (Path)</strong></p>
<p>The structure is based on the path variable. This means that we do not have to explicitly tell Episerver who our parent is instead this is done for us by the MenuAssembler class that parses all items and their path property, and thus makes a menu structure for us. As I wrote in the beginning, all menu items except the product name item has to have a parent. An easy way to test this is if you omit the second item (Sessions) and run the code again. This will result in the menu being empty as no relationship can be found for the active menu item. <br /><img src="/link/34c8d3aa2925421ba2671691851a80a1.aspx" /></p>
<p><strong>Empty url (Url)</strong></p>
<p>Under the MVC Controller I added an empty method that only redirected to the active method. The reason for this is that if you don't supply a URL then the menu provider will pick the first child item and use that as the url. This might be fine but in many cases you want to control the menu and don't leave it up to the framwork to determine the url. So it is advisable to always supply a url even if it is optional.</p>
<p><strong>Visibility (IsAvailable)</strong></p>
<p>Lastly it is always important to include who should see this menu item. If this is not set then everyone will see the menu item which might not be what is intended. This should not be confused with access rights. Access to the method is controlled by the MVC Controller/Method and the Authorize attribute. The IsAvailable is for visualization only.</p>
<p><em>Example: IsAvailable = (_) => PrincipalInfo.CurrentPrincipal.IsInRole("WebAdmins")</em></p>
<p><em>Tip: If you use the MenuItemAttribute then this will be detected automatically if you have an AuthorizeAttribute tag for the specific method. <br /><br /></em></p>
<p><strong>Different menues</strong></p>
<p>In the example I used the MenuPaths.Global constant. There are however a few more possibilities like MenuPaths.Help, MenuPaths.User and MenuPaths.UserSettings that lets you put your menu items in different areas.<br /><br /></p>
<h2>Inner workings</h2>
<p>The inner working of Episerver is fetching all MenuProviders using reflection. (What, did you think yours was the only menu provider? No there are plenty of them and they are used to present different parts of the UI.) During start-up, all providers are loaded using the ServiceLocator - if they expose the attribute MenuProvider.</p>
<p>There are a few classes that are responsible for the menu such as the MenuHelper (responsible for generating the menu code), NavigationService (middle layer that loads the menu items and returns them in the correct order to the MenuHelper) and the MenuAssembler (responsible for loading all MenuProviders and organize the menu items and their relations).</p>
<p>Items are picked on a first come, first serve basis - same as routes in MVC - which means that, if you have 2 items that matches then the first one will be picked not the second one.</p>
<p><strong>Why is this relevant?</strong></p>
<p>Well you might see a different result than you expected to see. The reason is that the MenuAssembler is organized by depth of the path (/ = depth 1, /education = depth 2, /education/sessions = depth 3 etc) and not by the property sort order. Which means that if you have 2 items then the one with the shortest path will be picked before the one with the longer path. Therefore try to always have unique urls.</p>
<p>Avoid this:<br />/education/sessions<br />/education/sessions/list</p>
<p>In favor of:<br />/education/sessions/active<br />/education/sessions/list </p>
<h2>Wrapup</h2>
<p>I hope that you have gotten better understanding of the menu and how to create one yourself.</p>
<p>Finally, the cleaver one will have noticed that I have not included dynamic routes in the tutorial above. The reason is that this requires some extra effort and you can read about it in my blog post <a href="/link/9fbdf19272cd4d72b5f5fd43fb19dcf0.aspx">here</a>.</p>Dynamic UI menu in MVC/blogs/patrik-fomin/dates/2020/7/explanation-dynamic-ui-menu-in-mvc/2020-07-31T12:41:55.0000000Z<p>I am writing this blog post to explain how to create a MVC UI menu in Episerver that works with dynamic routes. In this blog post, I will go through one way to achieve this by using a MenuProvider, however there is another way to achieve this by using the MenuItemAttribute. I will not go down but not go into too much detail on that as it requires more work. Also worth noting is that this solution has a minor UI issue due to a limitation that currently exist in Episerver UI 11.27.</p>
<p>If you are interested in brushing up on your menu item creation skills or want an example on how to create a UI menu, then take a look at my other blog post "<span><a href="/link/438251e81579451ea532dd50166ffc74.aspx">Creating your own menu inside the Episerver UI using MVC</a>"</span>.</p>
<h3>Menu items</h3>
<p>Before I go into detail on how to create a dynamic menu I thought I should quickly go over how a normal menu look and work. This is the corner stone to make it work for dynamic menues.</p>
<p>As mentioned in my other post, adding an item is very easy. All you need to do is to create a class with the attribute MenuProvider and then inherit the IMenuProvider interface.</p>
<p>Each menu item can then be created by adding a new MenuItem to the output list of menu items. I.e.</p>
<p><code>items.Add(new UrlMenuItem("{name}", MenuPaths.Global + "/{path}", "{url}")</code><br /><code>{</code><br /><code> SortIndex = 1000,</code><br /><code> IsAvailable = (_) => true</code><br /><code>});</code></p>
<p>(A UrlMenuItem is an extended menu item which exposes a new constructor for accepting the url parameter, nothing else.)</p>
<p>The menu is then built based on all these menu items in all MenuProviders. Each MenuProvider get's pulled in from the ServiceLocator where they are registered during startup and parsed in the MenuAssembler class. As I mentioned in my other blog post, if you do not have a parent then the ServiceNavigator class will be unable to construct the menu, which will result in a blank menu. (Order of methods being called: cshtml file whitch has @Html.Raw(Html.CreatePlatformNavigationMenu()) > MenuHelper class > NavigationService class > MenuAssembler class.)</p>
<p>There is no caching taking place in the MenuAssembler class but instead, for each specific call from the ServiceNavigtor class (Product name, menu level one and menu level two), a call is made to the MenuAssembler class which loads all menu providers and returns a list of matching menu items.</p>
<h2>Dynamic menu</h2>
<p>Once we have decided what menu items we want then we need to register them in our MenuProvider class so that the correct one can be picked when the page is loaded, and it's here where the challenge lies. With the current implementation of Menuitem this is not possible as the check is based on if we match the entire url (which we don't if we have a dynamic route).</p>
<p><strong>No dynamic route (works)</strong></p>
<p>Example (no dynamic argument) which yields the url: {site}/education/sessions/view</p>
<p>Controller class:</p>
<p><code>[RoutePrefix("education/sessions")]</code><br /><code>public class EducationSessionController : Controller</code><br /><code>{</code><br /><code> [Route("view"), HttpGet]</code><br /><code> public ActionResult View()</code><br /><code> {</code><br /><code> return View();</code><br /><code> }</code><br /><code>}</code></p>
<p>MenuProvider class:</p>
<p><code>items.Add(new UrlMenuItem("View session", MenuPaths.Global + "/education/sessions/view", "/education/sessions/view")</code><br /><code>{</code><br /><code> SortIndex = 1000,</code><br /><code> IsAvailable = (_) => true</code><br /><code>});</code></p>
<p><strong>Dynamic route (does not work)</strong></p>
<p>Example (dynamic argument id) which yields the url: {site}/education/sessions/view/{id}</p>
<p>Controller class:</p>
<p><code>[RoutePrefix("education/sessions")]</code><br /><code>public class EducationSessionController : Controller</code><br /><code>{</code><br /><code> [Route("view/{sessionId:int}"), HttpGet]</code><br /><code> public ActionResult View(int sessionId)</code><br /><code> {</code><br /><code> return View();</code><br /><code> }</code><br /><code>}</code></p>
<p>MenuProvider class:</p>
<p><code>items.Add(new UrlMenuItem("View session", MenuPaths.Global + "/education/sessions/view", "/education/sessions/view")</code><br /><code>{</code><br /><code> SortIndex = 1000,</code><br /><code> IsAvailable = (_) => true</code><br /><code>});</code></p>
<p>As you most likely already have figured out. There is no way of informing the MenuItem that this item (incoming route) is dynamic. What we want is a way to express the following route <code>/education/sessions/view/*</code> where the star represents a dynamic route. </p>
<h3>Solution</h3>
<p>In order to achieve this, we need to make the MenuItem aware about dynamic routes and act accordingly. Luckily this is very simple to achieve (barring for the one UI display issue). </p>
<p>We start with creating a new class called UrlMenuItemExtended which inherit from the UrlMenuItem class. In this class we create a property called IsDynamic that informs us if we have a dynamic route or not. After this, we override the IsSelected method with our own custom implementation that can detect dynamic urls.</p>
<p>The code looks like this:</p>
<p><code>using System.Web.Routing;</code><br /><code>using EPiServer.Shell;</code><br /><code>using EPiServer.Shell.Navigation;</code></p>
<p><code>namespace Episerver.Sessions</code><br /><code>{</code><br /><code> public class UrlMenuItemExtended : UrlMenuItem</code><br /><code> {</code><br /><code> public bool IsDynamic { get; }</code></p>
<p><code> public UrlMenuItemExtended(string text, string path, string url, bool isDynamic = false) : base(text, path, url)</code><br /><code> {</code><br /><code> IsDynamic = isDynamic;</code><br /><code> }</code></p>
<p><code> public override bool IsSelected(RequestContext requestContext)</code><br /><code> {</code><br /><code> Validate.RequiredParameter(nameof(requestContext), (object)requestContext);</code><br /><code> if (string.IsNullOrEmpty(Url))</code><br /><code> return false;</code></p>
<p><code> var menuItemUrl = this.Url.Trim('/').ToLowerInvariant();</code><br /><code> var browserUrl = requestContext.HttpContext.Request.Path.Trim('/').ToLowerInvariant();</code></p>
<p><code> // If we have a dynamic menu item, check with StartsWith</code><br /><code> if (IsDynamic && browserUrl.StartsWith(menuItemUrl))</code><br /><code> return true;</code></p>
<p><code> // If we have a static menu item, check with Equals</code><br /><code> if (!IsDynamic && browserUrl.Equals(menuItemUrl))</code><br /><code> return true;</code></p>
<p><code> return false;</code><br /><code> }</code><br /><code> }</code><br /><code>}</code></p>
<p>To use this we simply change from UrlMenuItem to UrlMenuItemExtended in the MenuProvider class.</p>
<p><code>items.Add(new UrlMenuItemExtended("View session", MenuPaths.Global + "/education/sessions/view", "/education/sessions/view", true)</code><br /><code>{</code><br /><code> SortIndex = 1000,</code><br /><code> IsAvailable = (_) => true</code><br /><code>});</code></p>
<p>This now matches everything up until /education/sessions/view but ignores the dynamic route values if the dynamic flag is set to true, otherwise it falls back to the original implementation.</p>
<p><em>Important: The order of which you add the items matter. This is because the MenuAssembler class sorts all items by depth. </em></p>
<p>If you have 2 items with the same depth (see a reference to my other blog at the top if you are unsure about depth) then the order of which they were added matter. Easiest to avoid this is by having unique urls. </p>
<p>Avoid this:<br />/education/sessions/1<br />/education/sessions/list</p>
<p>In favor of:<br />/education/sessions/view/1<br />/education/sessions/list </p>
<p><strong>More advanced routing</strong></p>
<p>If you need to do more advanced routing then you will need to use the RouteValues provided in the incoming parameter <code>requestContext</code> to do your matching. In this case I would suggest that you use the existing class <code>RouteMenuItem</code> which - instead of the Dynamic flag - accepts a RouteValueDictionary where you can populate the required data. These are then available to you in the <code>IsSelected</code> method.</p>
<h3>The UI issue</h3>
<p>As I mentioned earlier. There is one UI related issue and that is the link that is generated in the menu (<a href>). This means that you will get a menu item with a url that points to a method that doesn't have the dynamic route (I.e. the url.) So you will need to create a method that redirects any incoming calls to a list or similar method. I.e. the link in the menu will point to {site}<code>/education/sessions/view</code> and <strong>not</strong> to {site}<code>/education/sessions/view/id</code> or a url specified by you. The most optimal solution would have been to have a display url that you could populate to change where the people go when they click on the url.</p>
<p>So how do we get around this? (assuming that we don't want to create a method to handle this.)</p>
<h3>Aha moment</h3>
<p>It sounded like a trivial task to solve and that's what I thought as well. I checked through the code and saw that I could override a method (inside my UrlMenuItemExtended class) called <code>RenderContents</code>. This would then be called instead of the base implementation to render the menu item.</p>
<p>I copied the code from the MenuItem RenderContents method and pasted it into my overriden version in the UrlMenuItemExtended class, put a breakpoint and hit debug. Visual Studio first stopped at the IsSelected method so that I could verify that my implementation of dynamic menues worked which it did. I hit F5 to continue to the render method... or so I thought. Visual Studio finished rendering the site and left me with a menu that pointed towards the {site}<code>/education/sessions/view</code> url.</p>
<p>At first I thought I had missed something and tried it again but got the same result. I started going through the code to find why it behaved like this, and that is when I stumbled upon the fact that the <code>RenderContents</code> method is not used anymore (one annoying fact with Episerver's decision of forever backwards compability, a lot of obsolete code.) Since it is no longer in use (for this purpose), it of course does not get called in newer versions of the CMS UI. Instead, the javascript at the frontend calls an API endpoint which in turn call the NavigationService -> MenuAssembler classes to return the links in json form. This means that the html/css are now rendered in the frontend instead of in the old Render method. </p>
<p>There is no way to override the creation of the menu items as those reside in a class that can't be overridden. Until this is resolved, there is no way around this even if you create your own NavigationService class (where the mapping takes place). At least not until this has been resolved in the CMS UI package.</p>
<h3>MenuItem attribute</h3>
<p>I mentioned at the top that there are two ways to implement dynamic routes. This is true, however this only applies if you have created your own shell module. Meaning, if you want to create everything in one project then this is not possible as the shell module responsible for this to work, points to the shell modules folder. Unless your controllers use that as their routed url, it won't work.</p>
<p>You can read more about the MenuItem attribute <a href="/link/1b13247ab7954914b2484a0a48d44d29.aspx">here</a>.</p>
<p>In short, what this does is allowing you to register the MenuItem's using an attribute instead. Like this:</p>
<p><code>[RoutePrefix("education/sessions")]</code><br /><code>public class EducationSessionController : Controller</code><br /><code>{</code><br /><code> [Route("view/{sessionId:int}"), HttpGet]<br /> [MenuItem(MenuPaths.Global + "/education/sessions/view", Text = "View sessions")]<br /></code><code></code><code> public ActionResult View(int sessionId)</code><br /><code> {</code><br /><code> return View();</code><br /><code> }</code><br /><code>}</code></p>
<p>This is automatically infered resolved using a MenuProvider under the hood called <code>ReflectingMenuItemProvider</code>. This is loaded along with all other modules, and checks all controller methods for the MenuItem attribute using reflection. If it finds a MenuItem attribute it creates a RouteMenuItem that passes along a RouteValueDictionary object which is then used to validate the <code>IsSelected</code> method.</p>
<p>I hope that this blog post has clarified some of the mysteries with creating UI menu items for dynamic MVC routes.</p>