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, andUpdate. - 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}_Editfor editor. - Use
Initialize<TModel>to create shapes with a view model. - Register drivers in
Startup.csusingservices.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.cshtmlforCampaignAction_EditandCampaignAction.SummaryAdmin.cshtmlforCampaignAction_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.cshtmlincluding*Settings.Edit.cshtml), use theocat-*(Orchard Core Admin Theme) CSS classes instead of rawmb-3,form-label, or hard-coded grid classes. Useocat-wrapperfor the outer row,ocat-labelfor labels,ocat-endfor the input column,ocat-end-offsetfor checkbox-only rows/headings/buttons with no left label,ocat-limited-wrapper+ocat-limitedfor 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 andTheAdminThemeOptionsclass have been removed. Use the staticocat-*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.cshtmlfor the rootCampaignAction_Editshape.Views/CampaignAction.SummaryAdmin.cshtmlfor the rootCampaignAction_SummaryAdminshape.
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.cshtmlCampaignAction_Buttons_SummaryAdmin→Views/Items/CampaignAction.Buttons.SummaryAdmin.cshtmlCampaignActionFields_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") // 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.