orchardcore-navigation
Skill for creating and managing navigation menus in Orchard Core. Covers menu creation, navigation providers, admin menus, breadcrumbs, and menu content types.
Files2
View on GitHubSKILL.mdsrc/CrestApps.AgentSkills/orchardcore/orchardcore-navigation/SKILL.md
GitHubOrchard Core Navigation - Prompt Templates
Create Navigation and Menus
You are an Orchard Core expert. Generate navigation providers, menus, and pagers for Orchard Core.
Guidelines
OrchardCore.Navigationis always enabled. It provides theNavigation,Breadcrumb,Pager, andPagerSlimshapes; the APIs live inOrchardCore.Navigation.Core.- Content-managed menus are
Menucontent items fromOrchardCore.Menu(seeorchardcore-menus). - Code-first menu items come from
INavigationProviderimplementations. Inherit fromAdminNavigationProviderfor the admin menu orNamedNavigationProviderfor any other named menu, and register withservices.AddNavigationProvider<T>(). - Use
PrefixPosition()for alphabetical ordering inside a group, or explicit positions ("1","1.1") when order matters. Lower positions come first. - Breadcrumb trails are not built by navigation providers; use the
<breadcrumb>tag helper (seeorchardcore-breadcrumbs).
Enabling Menu Features
{
"steps": [
{
"name": "Feature",
"enable": [
"OrchardCore.Menu",
"OrchardCore.AdminMenu"
],
"disable": []
}
]
}
Custom Admin Navigation Provider
using Microsoft.Extensions.Localization;
using OrchardCore.Navigation;
public sealed class AdminMenu : AdminNavigationProvider
{
private readonly IStringLocalizer S;
public AdminMenu(IStringLocalizer<AdminMenu> localizer)
{
S = localizer;
}
protected override ValueTask BuildAsync(NavigationBuilder builder)
{
builder
.Add(S["Settings"], settings => settings
.Add(S["{{SubMenuName}}"], S["{{SubMenuName}}"].PrefixPosition(), item => item
.Id("{{menuId}}")
.Action("Index", "Admin", "{{ModuleName}}")
.Permission({{PermissionsClass}}.{{PermissionName}})
.LocalNav()
)
);
return ValueTask.CompletedTask;
}
}
- Top-level admin groups are Content, Design, Search, Settings, Tools, and Access Control.
NavigationConstantskeepsAdminMenuContentPosition(1),AdminMenuDesignPosition(2),AdminMenuSearchPosition(6), andAdminMenuAccessControlPosition(7), plusAdminId(admin),AdminMenuId, andCssClassPrefix(icon-class-). Permission(...)(chainable, any-of) andResource(...)hide items from unauthorized users; an unauthorized item's children are removed with it.- Add an icon with
.Id("myid")plus aViews/NavigationItemText-myid.Id.cshtmltemplate, notAddClasswith Font Awesome classes.
Front-End Navigation Provider
public sealed class MainMenu : NamedNavigationProvider
{
private readonly IStringLocalizer S;
public MainMenu(IStringLocalizer<MainMenu> localizer)
: base("main")
{
S = localizer;
}
protected override ValueTask BuildAsync(NavigationBuilder builder)
{
builder.Add(S["Notifications"], notifications => notifications
.Action("Index", "Notifications", "My.Module")
.LocalNav());
return ValueTask.CompletedTask;
}
}
Render it from a theme layout:
@await DisplayAsync(await New.Navigation(MenuName: "main", RouteData: ViewContext.RouteData))
Registering Navigation Provider
public sealed class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddNavigationProvider<AdminMenu>();
services.AddNavigationProvider<MainMenu>();
}
}
Navigation Theming
TheAdmin overrides Navigation-admin.cshtml, NavigationItem-admin.cshtml, and NavigationItemLink-admin.cshtml. Available alternates are Navigation__{MenuName}, NavigationItem__level__{level}, NavigationItem__{MenuName}, NavigationItem__{MenuName}__level__{level}, the same three for NavigationItemLink, and NavigationItemText-{id}.Id for an item with an id (e.g. NavigationItem-main-level-2.cshtml). Navigation item shapes are NavigationItemViewModel instances (Text, Href, Target, Item, Level, Selected, HasItems, ...), so templates can cast Model to it instead of using dynamic access.
Pagers
Pager and PagerSlim
public async Task<IActionResult> List(PagerParameters pagerParameters)
{
// Validates ?pageSize= against the allowed page sizes; falls back to the site default.
var pager = new Pager(pagerParameters, _pagerOptions);
var query = _session.Query<ContentItem, ContentItemIndex>(x => x.Published);
var items = await query.Skip(pager.GetStartIndex()).Take(pager.PageSize).ListAsync();
var routeData = new RouteData();
var pagerShape = await _shapeFactory.PagerAsync(pager, await query.CountAsync(), routeData);
return View(new MyListViewModel { Items = items, Pager = pagerShape });
}
- Inject
IOptions<PagerOptions>; the always-enabled Settings module post-configures it from the General site settings (PageSize,MaxPageSize,MaxPagedCount,AllowPageSizeSelection,PageSizeOptions). PagerAsync(pager, totalItemCount, routeData | routeValues)andPagerSlimAsync(pagerSlim[, values])areIShapeFactoryextensions inOrchardCore.DisplayManagement.Pagerparameters includePage,PageSize,TotalItemCount,Quantity,FirstText,PreviousText,NextText,LastText,GapText,PagerId(alternatePager__{PagerId}, e.g.Pager-MainBlog.cshtml), andShowNext. Customize parts withPager_First,Pager_Previous,Pager_Next,Pager_Last,Pager_Gap, andPager_CurrentPage(all morphed intoPager_Link).PagerSlimrenders cursor-based Previous/Next links (Before/After) withPreviousClass,NextClass,PreviousText,NextText, andUrlParams.
Page Size Selection
When Page size selection is enabled in Configuration → Settings → General, Pager and PagerSlim render an Items per page selector next to the links on admin and front-end lists. The value travels as ?pageSize= and is honored only when selection is enabled and the value is one of the allowed sizes (sorted, de-duplicated positive numbers, default 10, 25, 50, 100); otherwise the default page size applies. Pager links keep the selection. When disabled (the default), pagers render as before.
{
"steps": [
{
"name": "settings",
"AllowPageSizeSelection": true,
"PageSizeOptions": [ 10, 20, 50 ]
}
]
}
Opt custom listings into the same validation:
| API | Use |
|---|---|
new Pager(PagerParameters, PagerOptions) |
Resolves the page and validated page size in one step. |
PagerOptions.GetPageSize() |
Site default, capped by MaxPageSize. |
PagerOptions.GetPageSize(int? selected) / GetPageSize(int? selected, int defaultPageSize) |
Validated selection, or the given default (e.g. a list's own page size). |
PagerOptions.AllowPageSizeSelection, PageSizeOptions |
The site settings. |
PagerSlimParameters.PageSize |
Optional selection for cursor-based pagers: new PagerSlim(pagerSlimParameters, _pagerOptions.GetPageSize(pagerSlimParameters.PageSize)). |
PageSizeSelector.BuildOptions(serviceProvider, currentPageSize) |
Builds the selector items to render the selector separately (then set ShowPageSizeSelector = false on the pager). |
The selector is the Pager_PageSizeSelector shape. OrchardCore.Navigation renders it by default as unstyled <div class="pager-page-size"> markup with a <label> and <select>; TheAdmin and TheTheme style it with Bootstrap. Override Views/Pager_PageSizeSelector.cshtml in a theme to restyle it. The shape receives PageSizes (List<SelectListItem>, Text = size, Value = URL, Selected = current) and CurrentPageSize; do not read Model.Items, which holds child shapes.
Pager defaults can also be set per tenant in appsettings.json under OrchardCore:Navigation:PagerOptions (OrchardCore_Navigation is deprecated), but the General settings override them.
SEO
Add rel="nofollow" to pager links with Model.Pager.Attributes["rel"] = "nofollow" (Razor) or {% shape_pager Model.Pager attributes: "{\"rel\": \"nofollow\"}" %} (Liquid).
Menu Recipe (Creating a Menu via Recipe)
{
"steps": [
{
"name": "Content",
"data": [
{
"ContentItemId": "main-menu",
"ContentType": "Menu",
"DisplayText": "Main Menu",
"Latest": true,
"Published": true,
"MenuPart": {},
"AliasPart": {
"Alias": "main-menu"
},
"MenuItemsListPart": {
"MenuItems": [
{
"ContentType": "LinkMenuItem",
"ContentItemId": "menu-home",
"DisplayText": "Home",
"LinkMenuItemPart": {
"Url": "~/"
}
},
{
"ContentType": "ContentMenuItem",
"ContentItemId": "menu-blog",
"DisplayText": "Blog",
"ContentMenuItemPart": {
"ContentItem": {
"ContentItemId": "{{BlogContentItemId}}"
}
}
}
]
}
}
]
}
]
}
Rendering Menu in Liquid Template
{% shape "Menu", alias: "alias:main-menu" %}
Upgrading from 3.x
NavigationHelper.UseLegacyFormat()and theLegacyAdminMenuNavigationswitch were removed, together withNavigationConstants.AdminMenuSecurityPosition,AdminMenuAuditTrailPosition,AdminMenuWorkflowsPosition,AdminMenuConfigurationPosition, andLegacyAdminMenuNavigationSwitchKey.NavigationHelper.PopulateMenuAsyncandPopulateMenuLevelAsyncnow takeIShapeFactoryandIShapeinstead ofdynamicarguments.- Replace manual
if (name != "admin")checks inINavigationProviderimplementations withAdminNavigationProviderorNamedNavigationProvider. - Use
new Pager(pagerParameters, pagerOptions)instead ofnew Pager(pagerParameters, siteSettings.PageSize)so page size selection works.
Related skills
- Use
orchardcore-menusfor built-in menu item types, shape alternates, and the<menu>tag helper. - Use
orchardcore-admin-menufor the admin-configurableOrchardCore.AdminMenunode tree. - Use
orchardcore-breadcrumbsfor the<breadcrumb>tag helper andIBreadcrumbProvider. - Use
orchardcore-admin-listsfor admin list pages, which render the pager and page size selector for you.