| name | orchardcore-display-management |
| description | 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. |
| license | Apache-2.0 |
| metadata | {"author":"CrestApps Team","version":"1.0"} |
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_SummaryAdmin → Views/Items/CampaignAction.Fields.SummaryAdmin.cshtml
CampaignAction_Buttons_SummaryAdmin → Views/Items/CampaignAction.Buttons.SummaryAdmin.cshtml
CampaignActionFields_Edit → Views/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")
.Location("Summary", "Meta:5")
.Location("SummaryAdmin", "Actions: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 =>
{
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:
...
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.