Orchard Core doesn't render HTML directly, but instead will usually render something called a Shape, which is an object that represents the thing to render and has all the necessary data and metadata to render HTML. When rendering a Shape, Orchard Core will look for specific templates, passing the Shape to this template. Orchard Core can match with many templates for the same Shape. These potential templates are called Alternates.
var factory = context.RequestServices.GetREquiredService<IDisplayHelper>(); var shape = await factory.CreateAsync("Car"); var displayHelper = context.RequestServices.GetRequiredService<IDisplayHelper>(); var htmlContent = await displayHelper.ShapeExecuteAsync(shape);Car.cshtml await using var sw = new StreamWriter(context.Response.Body); htmlContent.WriteTo(sw, HtmlEncoder.Default);Code is available on Github in the branch: Test_Shape_With_Razor_View
Liquid is a safe, customer-facing templating language originally created by Shopify. It's designed to be secure, flexible, and easy to understand, making it perfect for generating dynamic content where you need to combine static templates with variable data from your application.
To render shapes using a liquid templates we only add to dependencies OrchardCore.DisplayManagement.Liquid package and to Program.cs AddLiquidViews() service.
The shape as an instance of the IShape interface contains the Properties property of type IDictionary, where we can insert data via the index, which is then visible in the template. In the template, then we display data using the 'Model' object. The ShapeExecuteAsync method sends a Shape model to the template, which, in addition to IShape, is also an instance of the DynamicObject class of Compose containing the TryGetMember method. This allows us to access Properties directly, e.g., Model.Brand instead of Model.Properties["Brand"].
Code for this part is available on Github in the branch: Test_ShapeData_With_Liquid_Template
Since the dynamic approach requires some overhead, we will use a generic method CreateAsync to create the shape and use a POCO object as the type. In our case, the Car class.
public class Car
{
public string? Brand { get; set; }
public string? Color { get; set; }
}
var shape = await factory.CreateAsync<Car>("Car", c => { c.Brand = "Renault"; c.Color = "Red";});
Now we can type the model as Car and get the value directly from the class properties:
@using OrchardCore.DisplayManagement;
@model Car
This is a car @Model.Brand with @Model.Color color
As mentioned 'IShape' contains metadata, like Id, TagName, Classes, Attributes... , that can be used to render a shape by template. In a template we then render these metadata with helper class. Here is code setting metadata
shape.Id = "my-renault";
shape.TagName = "h3";
shape.Classes.Add("car");
shape.Classes.Add("brand-renault");
shape.Attributes.Add("data-brand", "renault");
and here templates rendering them:
@using OrchardCore.DisplayManagement;
@model Car
@{
var shape = Model as IShape;
var tagBuilder = shape.GetTagBuilder();
}
@tagBuilder.RenderStartTag()
This is a car @Model.Brand with @Model.Color
@tagBuilder.RenderEndTag()
The result is this content:

and if we look at the HTML code, we can see the rendered shape metadata:

Code for this part is available on Github in the branch: Test_Shape_With_Metadata