🧩 Framework Components
Custom Menu Views
📖 Docs

Custom Menu and Views - Optimizely CMS 12

Exam Area: Area 4 – Framework Components (15%)
Reference: https://docs.developers.optimizely.com/content-management-system/docs/extending-the-cms-ui


1. IMenuProvider - Add Menu Items

using EPiServer.Shell.Navigation;

[ServiceConfiguration(typeof(IMenuProvider))]
public class SiteMenuProvider : IMenuProvider
{
    public IEnumerable<MenuItem> GetMenuItems()
    {
        // Add to the Global CMS menu
        var rootItem = new SectionMenuItem("My Company Tools",
            MenuPaths.Global + "/mycompany");

        var toolItem = new UrlMenuItem(
            "Product Sync",
            MenuPaths.Global + "/mycompany/product-sync",
            "/episerver/mycompany/product-sync")
        {
            IsAvailable = request => request.User.IsInRole("ProductManagers"),
            SortIndex = 100,
            AuthorizationPolicy = CmsPolicy.ManageContent
        };

        yield return rootItem;
        yield return toolItem;
    }
}

2. MenuPaths

// Built-in menu paths
MenuPaths.Global                          // Top-level
MenuPaths.Global + "/cms"                // CMS menu
MenuPaths.Global + "/cms/edit"           // Edit
MenuPaths.Global + "/cms/admin"          // Admin
MenuPaths.Global + "/cms/reports"        // Reports

3. Controller for Custom View

[Authorize(Roles = "WebAdmins,ProductManagers")]
public class ProductSyncController : Controller
{
    private readonly IProductSyncService _syncService;

    public ProductSyncController(IProductSyncService syncService)
    {
        _syncService = syncService;
    }

    // GET /episerver/mycompany/product-sync
    public IActionResult Index()
    {
        var model = new ProductSyncViewModel
        {
            LastSyncDate = _syncService.LastSyncDate,
            ProductCount = _syncService.GetProductCount()
        };
        return View(model);
    }

    // POST
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Sync()
    {
        await _syncService.SyncAsync();
        TempData["Success"] = "Sync completed!";
        return RedirectToAction("Index");
    }
}

4. View for Custom Tool

@* Views/ProductSync/Index.cshtml *@
@model ProductSyncViewModel

@{
    Layout = "~/Views/Shared/_CmsLayout.cshtml";  // Use CMS shell layout
}

<div class="epi-formArea">
    <h1>Product Sync Tool</h1>
    
    <dl>
        <dt>Last sync</dt>
        <dd>@Model.LastSyncDate?.ToString("dd MMM yyyy HH:mm")</dd>
        <dt>Products</dt>
        <dd>@Model.ProductCount</dd>
    </dl>

    @using (Html.BeginForm("Sync", "ProductSync", FormMethod.Post))
    {
        @Html.AntiForgeryToken()
        <button type="submit" class="epi-cta">Sync Now</button>
    }
</div>

5. AdminPlugin Attribute (Legacy)

// Legacy approach - CMS 11 style
[AdminPlugin(
    "My Plugin",
    Description = "A custom plugin",
    SortIndex = 2000,
    Area = PlugInArea.AdminMenu,
    Url = "~/mycompany/my-plugin")]
public class MyPlugin { }

6. View Registration (Single Page App)

// Shell module view (Dojo-based)
[ServiceConfiguration(typeof(ViewConfiguration))]
public class MyCustomViewConfiguration : ViewConfiguration<MyContentType>
{
    public MyCustomViewConfiguration()
    {
        Key = "my-custom-view";
        Name = "My View";
        Description = "Custom view description";
        ControllerType = "myModule.Views.MyView";  // Dojo module path
        IconClass = "epi-iconObjectsMedia";
    }
}

7. Custom Report

// Reports in CMS Admin
[ServiceConfiguration(typeof(IReport))]
public class ContentAuditReport : IReport
{
    public string Name => "Content Audit";
    public string Description => "Audit content in the system";
    public string Category => "Content";
    public string Area => ReportArea.Cms;
    public string ControllerTypeName => typeof(ContentAuditController).FullName;
}

[Authorize(Roles = "WebAdmins")]
public class ContentAuditController : Controller
{
    public ActionResult Index()
    {
        return View(GetAuditData());
    }
}

Review Questions

  1. What is the IMenuProvider interface used for? (Adding items to the CMS navigation menu)
  2. Where does MenuPaths.Global + "/cms/admin" point? (The CMS Admin section in the menu)
  3. What role do custom tools typically require? (WebAdmins or custom roles)
  4. What is ViewConfiguration used for? (Registering custom views for content types)
  5. What is the IReport interface used for? (Creating custom reports in CMS Admin)