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.
You are an Orchard Core expert. Generate display drivers, shapes, and display management code for Orchard Core.
DisplayDriver.ContentPartDisplayDriver<TPart>.Display, Edit, and Update.IDisplayResult (shapes to render).{PartName} for display and {PartName}_Edit for editor.Initialize<TModel> to create shapes with a view model.Startup.cs using services.AddContentPart<TPart>().UseDisplayDriver<TDriver>().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)..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.*.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).@Orchard.GetWrapperClasses(), @Orchard.GetLabelClasses(), @Orchard.GetEndClasses() helper methods and TheAdminThemeOptions class have been removed. Use the static ocat-* CSS classes directly.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);
}
}
using OrchardCore.ContentManagement;
public class {{PartName}}ViewModel
{
public string {{PropertyName}} { get; set; }
public ContentItem ContentItem { get; set; }
}
@model {{Namespace}}.ViewModels.{{PartName}}ViewModel
<p>@Model.{{PropertyName}}</p>
@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>
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>();
}
}
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;
}
}
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.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.cshtmlCampaignAction_Buttons_SummaryAdmin → Views/Items/CampaignAction.Buttons.SummaryAdmin.cshtmlCampaignActionFields_Edit → Views/CampaignActionFields.Edit.cshtmlUse .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
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");
}
}
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;
}
}
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 directly through options:
services.Configure<ShapeRenderingOptions>(options =>
options.WriteShapeDebugInformation = true);
When OrchardCore.Settings is enabled, the site's debug settings configure the same ShapeRenderingOptions.WriteShapeDebugInformation value.
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.
npx skills add CrestApps/orchardcore-display-management下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer