Skip to content
Menu
Search
Browse documentation & this page

Developer Guide

Adding a section

Put a tab of your own on the CMS top bar, with pages your authors use, a sidebar and the standard page header.

The top bar of the CMS is extensible. A section you write gets a tab beside Edit, Audiences, Releases, Tasks and Reports, and its pages open inside the CMS shell with the same header as Cendia's own.

Use a section for screens your authors work in: a translation queue, an order list, a review board. Configuration an administrator sets once belongs on the Settings menu instead.

There is no registration call and no menu file to edit. The page's location and one attribute are all the CMS needs.

What the CMS does for you

For every Razor Page under your site's Extensions/Sections/ folder, the CMS:

  • serves it at /cms/sections/<folder>/<name>, in lowercase;
  • draws it inside the CMS shell: top bar, page header, and a sidebar when the section has one;
  • lets in Authors and Administrators only. Somebody signed in to your public site is not signed in to the CMS and is turned away. You cannot forget this and you cannot turn it off.

A page that also carries [SectionPage] puts its section on the top bar.

Step 1: create the page

Give the section a folder under Extensions/Sections/ and add a Razor Page to it. A folder's Index page is served at the folder's address.

YourSite/
  Extensions/
    Sections/
      _ViewImports.cshtml          ← ships with the Starter
      Translations/
        Index.cshtml               → /cms/sections/translations
        Index.cshtml.cs
        Vendors.cshtml             → /cms/sections/translations/vendors

The Starter already includes Extensions/Sections/_ViewImports.cshtml. If your site predates it, create it:

@using Cendia.Extensions
@namespace YourSite.Extensions.Sections
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Do not add a _ViewStart.cshtml or set Layout. The CMS supplies the layout for this folder.

Step 2: put it on the top bar

Give the PageModel [SectionPage]. It takes three things: the section (the tab), the menu name, and a subtitle.

using Cendia.Extensions;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace YourSite.Extensions.Sections.Translations;

[SectionPage("Translations", "Queue", "Pages waiting for a translator.",
    Title = "Translation Queue", Order = 10)]
public class QueueModel : PageModel
{
    public void OnGet() { }
}
Argument Required What it does
section yes The tab the page belongs to, as the top bar should read. Matching is case-insensitive.
menuName yes What the section's sidebar calls the page.
subtitle yes One line under the page title saying what the page is for.
Title no The page heading, when it should say more than the menu name. Defaults to the menu name.
Order no Where the page sits in its section's sidebar. The lowest is the page the tab opens.
TabOrder no Where the section's tab sits on the top bar. See step 3.
AdministratorOnly no true keeps the page, and its row in the sidebar, to Administrators.

A page without a PageModel carries the attribute in the .cshtml instead, with @attribute [SectionPage(...)]. Use one place, not both.

Step 3: choose where the tab sits

Cendia numbers its own tabs 10, 20, 30… from the left. Your TabOrder places your tab among them:

Tab Number
Edit 10
Audiences 20
Releases 30
Tasks 40
Reports 50
Settings 60
TabOrder Result
25 Between Audiences and Releases
45 Between Tasks and Reports
(left out) After Reports, before Settings
65 After Settings

TabOrder belongs to the section, so set it on one page. If several pages of a section set it, the lowest is used. If your tab has the same number as a Cendia tab, Cendia's comes first. Two tabs of your own with the same number are sorted by name.

The tab is in the same place for everyone who sees it, whichever of its pages they may open.

TabOrder is not Order. Order arranges the pages inside the section; TabOrder arranges the section on the top bar.

Step 4: add more pages

Every page naming the same section shares its tab.

  • One listed page: the tab opens it, and there is no sidebar.
  • Two or more: the section gets a sidebar listing them by Order, then by menu name.

Step 5: write the page body

Do not write a title or subtitle in the page. The CMS draws both from [SectionPage]. Write only what goes beneath them.

@page
@model YourSite.Extensions.Sections.Translations.QueueModel

@section PageActions {
    <button type="button" class="btn btn-primary btn-sm">Send All</button>
}

<table class="table table-sm">…</table>

@section PageActions, ViewData["CmsWidth"], forms, handlers and tag helpers all work as they do on a Settings page.

Who sees what

  • The tab is drawn for anyone who can open at least one of the section's pages.
  • An AdministratorOnly page is refused to an Author and left out of their sidebar. If that leaves them one page, they get no sidebar.
  • Content rights are yours to apply. The CMS decides who may open the page. What the page lists about pages, blocks or media should be filtered by what the author may read, with IAccessControl.

Detail pages

A page in the section's folder without [SectionPage] is still served and still protected, but it is not listed. Use this for a screen reached from a listed page. It keeps the section's tab lit, and sets its own header:

@{
    ViewData["Title"] = Model.Job.Name;
    ViewData["Subtitle"] = "One translation job and the pages in it.";
}

Rules to know

  • Addresses are lowercase and under /cms/sections/. Link to a section page by that address rather than with asp-page, since the page is not in the cms area. Nothing of Cendia's is served under /cms/sections/, so an upgrade cannot take an address you are using.
  • Name the folder after the section. Two sections cannot share a folder name, including one that arrives in a package.
  • It lives in Extensions, not Pages. Pages/ is your public site.
  • The Starter has a worked example. Extensions/Sections/Translations/SectionPage.Example.cs and .cshtml ship commented out.

From a package

A NuGet package adds a section the same way: a Razor Class Library with its pages under Extensions/Sections/<Folder>/, and one call from the site so the package's pages are served.

public static IServiceCollection AddAcmeTranslations(this IServiceCollection services) =>
    services.AddCendiaExtension<AcmeTranslationsMarker>();

The same call is where a package names its editor scripts. See Adding tree actions.