Skip to main content
AI/MLCrestApps

orchardcore-display-management

Skill for using Orchard Core's display management system. Covers display drivers, display managers, shapes, display types, shape table providers, placement, and editor/display mode patterns. Use this skill when requests mention Orchard Core Display Management, Create Display Drivers and Shapes, Content Part Display Driver Pattern, View Model Pattern, Display Shape View (Views/{{PartName}}.cshtml), Editor Shape View (Views/{{PartName}}_Edit.cshtml), or closely related Orchard Core implementation, setup, extension, or troubleshooting work. Strong matches include work with OrchardCore.DisplayManagement.Views, OrchardCore.ContentManagement, OrchardCore.ContentManagement.Handlers, DisplayDriver. It also helps with display management examples, Display Shape View (Views/{{PartName}}.cshtml), Editor Shape View (Views/{{PartName}}_Edit.cshtml), Registering a Display Driver, plus the code patterns, admin flows, recipe steps, and referenced examples captured in this skill.

Stars
13
Source
CrestApps/CrestApps.AgentSkills
Updated
2026-05-29
Slug
CrestApps--CrestApps.AgentSkills--orchardcore-display-management
View on GitHubRaw SKILL.md

// install — copy + paste into any project

mkdir -p .claude/skills && curl -fsSL https://raw.githubusercontent.com/CrestApps/CrestApps.AgentSkills/HEAD/plugins/orchardcore/skills/orchardcore-display-management/SKILL.md -o .claude/skills/orchardcore-display-management.md

Drops the SKILL.md into .claude/skills/orchardcore-display-management.md. Works with Claude Code, Cursor, and any agent that loads SKILL.md files from .claude/skills/.

Orchard Core Display Management - Prompt Templates

Create Display Drivers and Shapes

You are an Orchard Core expert. Generate display drivers, shapes, and display management code for Orchard Core.

Guidelines

  • Every content part that needs custom rendering requires a DisplayDriver.
  • Display drivers inherit from ContentPartDisplayDriver<TPart>.
  • Drivers handle three operations: Display, Edit, and Update.
  • Each operation returns IDisplayResult (shapes to render).
  • View models are used to pass data between drivers and views.
  • Shape names follow the convention {PartName} for display and {PartName}_Edit for editor.
  • Use Initialize<TModel> to create shapes with a view model.
  • Register drivers in Startup.cs using services.AddContentPart<TPart>().UseDisplayDriver<TDriver>().
  • For non-content-item models rendered through DisplayDriver<TModel>, the root shape still needs its own wrapper template for each display type you build (for example, CampaignAction.Edit.cshtml for CampaignAction_Edit and CampaignAction.SummaryAdmin.cshtml for CampaignAction_SummaryAdmin).
  • When driver results are placed into zones with .Location("Content:1"), .Location("Actions:5"), or similar, the wrapper template must render those zones (Model.Content, Model.Actions, Model.Meta, etc.) or the child shapes will never appear.
  • For ALL admin editor views (*.Edit.cshtml including *Settings.Edit.cshtml), use the ocat-* (Orchard Core Admin Theme) CSS classes instead of raw mb-3, form-label, or hard-coded grid classes. Use ocat-wrapper for the outer row, ocat-label for labels, ocat-end for the input column, ocat-end-offset for checkbox-only rows/headings/buttons with no left label, ocat-limited-wrapper + ocat-limited for narrow-width fields. Do NOT apply these classes to frontend views (Login, Register, etc.) or Admin Menu node editing (needs full width).
  • The former @Orchard.GetWrapperClasses(), @Orchard.GetLabelClasses(), @Orchard.GetEndClasses() helper methods and TheAdminThemeOptions class have been removed. Use the static ocat-* CSS classes directly.
  • Always seal classes.

Content Part Display Driver Pattern

using OrchardCore.ContentManagement.Display.ContentDisplay;
using OrchardCore.ContentManagement.Display.Models;
using OrchardCore.DisplayManagement.ModelBinding;
using OrchardCore.DisplayManagement.Views;

public sealed class {{PartName}}DisplayDriver : ContentPartDisplayDriver<{{PartName}}>
{
    public override IDisplayResult Display({{PartName}} part, BuildPartDisplayContext context)
    {
        return Initialize<{{PartName}}ViewModel>("{{PartName}}", model =>
        {
            model.{{PropertyName}} = part.{{PropertyName}};
            model.ContentItem = part.ContentItem;
        })
        .Location("Detail", "Content:5")
        .Location("Summary", "Content:5");
    }

    public override IDisplayResult Edit({{PartName}} part, BuildPartEditorContext context)
    {
        return Initialize<{{PartName}}ViewModel>("{{PartName}}_Edit", model =>
        {
            model.{{PropertyName}} = part.{{PropertyName}};
            model.ContentItem = part.ContentItem;
        });
    }

    public override async Task<IDisplayResult> UpdateAsync({{PartName}} part, UpdatePartEditorContext context)
    {
        var model = new {{PartName}}ViewModel();

        await context.Updater.TryUpdateModelAsync(model, Prefix);

        part.{{PropertyName}} = model.{{PropertyName}};

        return Edit(part, context);
    }
}

View Model Pattern

using OrchardCore.ContentManagement;

public class {{PartName}}ViewModel
{
    public string {{PropertyName}} { get; set; }
    public ContentItem ContentItem { get; set; }
}

Display Shape View (Views/{{PartName}}.cshtml)

@model {{Namespace}}.ViewModels.{{PartName}}ViewModel

<p>@Model.{{PropertyName}}</p>

Editor Shape View (Views/{{PartName}}_Edit.cshtml)

@model {{Namespace}}.ViewModels.{{PartName}}ViewModel

<div class="ocat-wrapper" asp-validation-class-for="{{PropertyName}}">
    <label asp-for="{{PropertyName}}" class="ocat-label">{{DisplayLabel}}</label>
    <div class="ocat-end">
        <input asp-for="{{PropertyName}}" class="form-control" />
        <span asp-validation-for="{{PropertyName}}"></span>
    </div>
</div>

Registering a Display Driver

using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Display.ContentDisplay;

public sealed class Startup : StartupBase
{
    public override void ConfigureServices(IServiceCollection services)
    {
        services.AddContentPart<{{PartName}}>()
            .UseDisplayDriver<{{PartName}}DisplayDriver>();
    }
}

Content Part with Handler Pattern

using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Handlers;

public sealed class {{PartName}}Handler : ContentPartHandler<{{PartName}}>
{
    public override Task InitializingAsync(InitializingContentContext context, {{PartName}} part)
    {
        part.{{PropertyName}} = "default value";
        return Task.CompletedTask;
    }
}

Display Types

Orchard Core uses display types to differentiate how content is rendered:

  • Detail — Full content display (e.g., a blog post page).
  • Summary — Abbreviated display (e.g., in a list).
  • SummaryAdmin — Admin-specific summary view.
  • Edit — Editor form for the content part.

Wrapper Templates for DisplayDriver<TModel>

CRITICAL: When you use DisplayDriver<TModel> (not ContentPartDisplayDriver), Orchard Core resolves a root shape for each display type. If this root shape template does not exist, you get InvalidOperationException: The shape type '{ModelName}_Edit' is not found. You must create wrapper templates for every display type the driver builds shapes for.

Required wrapper templates per display type:

Display type built by driver Required wrapper template file
Edit (via Edit() / BuildEditorAsync) Views/{ModelName}.Edit.cshtml
SummaryAdmin (via DisplayAsync + Location("Content:1")) Views/{ModelName}.SummaryAdmin.cshtml
Detail Views/{ModelName}.cshtml
Summary Views/{ModelName}.Summary.cshtml

The wrapper template renders the zones that child shapes are placed into. If the wrapper doesn't render a zone (e.g., Model.Content, Model.Actions, Model.Meta), the child shapes placed in those zones will be silently dropped.

Example driver:

internal sealed class CampaignActionDisplayDriver : DisplayDriver<CampaignAction>
{
    public override Task<IDisplayResult> DisplayAsync(CampaignAction model, BuildDisplayContext context)
    {
        return CombineAsync(
            View("CampaignAction_Fields_SummaryAdmin", model)
                .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, "Content:1"),
            View("CampaignAction_Buttons_SummaryAdmin", model)
                .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, "Actions:5")
        );
    }

    public override IDisplayResult Edit(CampaignAction model, BuildEditorContext context)
        => Initialize<CampaignActionViewModel>("CampaignActionFields_Edit", m => { })
            .Location("Content:1");
}

Required wrapper templates:

  • Views/CampaignAction.Edit.cshtml for the root CampaignAction_Edit shape.
  • Views/CampaignAction.SummaryAdmin.cshtml for the root CampaignAction_SummaryAdmin shape.

Minimal edit wrapper:

@if (Model.Content != null)
{
    @await DisplayAsync(Model.Content)
}

Summary admin wrapper with zones:

<div class="row g-0">
    <div class="col-lg col-12">
        @if (Model.Content != null)
        {
            @await DisplayAsync(Model.Content)
        }

        @if (Model.Meta != null)
        {
            @await DisplayAsync(Model.Meta)
        }
    </div>
    <div class="col-lg-auto col-12">
        @if (Model.Actions != null)
        {
            @await DisplayAsync(Model.Actions)
        }
    </div>
</div>

Child shape file-name mapping:

  • CampaignAction_Fields_SummaryAdminViews/Items/CampaignAction.Fields.SummaryAdmin.cshtml
  • CampaignAction_Buttons_SummaryAdminViews/Items/CampaignAction.Buttons.SummaryAdmin.cshtml
  • CampaignActionFields_EditViews/CampaignActionFields.Edit.cshtml

Placing Shapes in Zones

Use .Location() to place shapes in zones with positions:

return Initialize<MyViewModel>("MyShape", model => { ... })
    .Location("Detail", "Content:5")      // Detail view, Content zone, position 5
    .Location("Summary", "Meta:5")        // Summary view, Meta zone, position 5
    .Location("SummaryAdmin", "Actions:5"); // Admin summary, Actions zone, position 5

Content Field Display Driver

using OrchardCore.ContentManagement.Display.ContentDisplay;
using OrchardCore.ContentManagement.Display.Models;

public sealed class {{FieldName}}FieldDisplayDriver : ContentFieldDisplayDriver<{{FieldName}}Field>
{
    public override IDisplayResult Display({{FieldName}}Field field, BuildFieldDisplayContext context)
    {
        return Initialize<{{FieldName}}FieldViewModel>(
            GetDisplayShapeType(context),
            model =>
            {
                model.Field = field;
                model.Part = context.ContentPart;
                model.PartFieldDefinition = context.PartFieldDefinition;
            })
            .Location("Detail", "Content")
            .Location("Summary", "Content");
    }
}

Shape Table Provider

Override shape rendering behavior:

using OrchardCore.DisplayManagement.Descriptors;

public sealed class MyShapeTableProvider : IShapeTableProvider
{
    public ValueTask DiscoverAsync(ShapeTableBuilder builder)
    {
        builder.Describe("Content")
            .OnDisplaying(context =>
            {
                // Add alternates, wrappers, etc.
                context.Shape.Metadata.Alternates.Add("Content__{{ContentType}}");
            });

        return ValueTask.CompletedTask;
    }
}

Shape Debug Information

You can instruct Orchard Core to write HTML comments around rendered shapes. This makes it easier to identify which Razor or Liquid template produced a specific fragment in the page output.

Enable during startup:

services
    .AddOrchardCms()
    .AddShapeDebugInformation();

Or enable directly through options:

services.Configure<ShapeRenderingOptions>(options =>
    options.WriteShapeDebugInformation = true);

When enabled, rendered shapes are wrapped with HTML comments:

<!--shape-start type:Menu bindings:Menu__Main=>Themes/Contoso/Views/Menu-Main.cshtml (razor)-->
...
<!--shape-end type:Menu-->

The start comment contains the shape type and the binding that was used. Razor bindings report the .cshtml path, while Liquid bindings report a virtual .liquid source. This is useful during development to trace which template is responsible for each fragment of the rendered page.