Module: Page Builder
6 of 15 Pages
Style the template
You probably noticed in the last section that the template is not very pretty yet. You’ll also find that the dropdowns for color scheme and corner style in the template properties don’t change anything.
Let’s create a service that maps the options from the dropdown to CSS classes, and a tag helper that uses it to style a div element.
Create a service to retrieve styles
- Define an
IComponentStyleEnumServiceinterface in the ~/Features/Shared/Services folder. - Include method signatures to retrieve CSS classes based on a
ColorSchemeOptionandCornerStyleOptionrespectively.C#IComponentStyleEnumService.cs... namespace TrainingGuides.Web.Features.Shared.Services; public interface IComponentStyleEnumService { IEnumerable<string> GetColorSchemeClasses(ColorSchemeOption colorScheme); IEnumerable<string> GetCornerStyleClasses(CornerStyleOption cornerStyle); ... - Add method signatures to convert the string representation of a selected option to its corresponding enum value.
C#IComponentStyleEnumService.cs
... CornerStyleOption GetCornerStyle(string cornerStyleString); ColorSchemeOption GetColorScheme(string colorSchemeString); } - Implement the interface, mapping the selected options to sets of CSS classes. Use the
Parsemethod from theEnumStringServicewe defined earlier in the series for the string conversion methods.C#ComponentStyleEnumService.csusing TrainingGuides.Web.Features.Shared.OptionProviders; using TrainingGuides.Web.Features.Shared.OptionProviders.CornerStyle; using TrainingGuides.Web.Features.Shared.OptionProviders.ColorScheme; namespace TrainingGuides.Web.Features.Shared.Services; public class ComponentStyleEnumService : IComponentStyleEnumService { public IEnumerable<string> GetColorSchemeClasses(ColorSchemeOption colorScheme) => colorScheme switch { ColorSchemeOption.Light1 => ["tg-bg-light-1", "tg-txt-dark"], ColorSchemeOption.Light2 => ["tg-bg-light-2", "tg-txt-dark"], ColorSchemeOption.Light3 => ["tg-bg-light-3", "tg-txt-dark"], ColorSchemeOption.Dark1 => ["tg-bg-primary", "tg-txt-light"], ColorSchemeOption.Dark2 => ["tg-bg-secondary", "tg-txt-light"], ColorSchemeOption.TransparentLight => ["tg-bg-none", "tg-txt-light"], ColorSchemeOption.TransparentMedium => ["tg-bg-none", "tg-txt-medium"], ColorSchemeOption.TransparentDark => ["tg-bg-none", "tg-txt-dark"], _ => [string.Empty], }; public IEnumerable<string> GetCornerStyleClasses(CornerStyleOption cornerStyle) => cornerStyle switch { CornerStyleOption.Round => ["tg-corner-rnd"], CornerStyleOption.VeryRound => ["tg-corner-v-rnd"], CornerStyleOption.Sharp => ["tg-corner-shrp"], _ => [string.Empty], }; public CornerStyleOption GetCornerStyle(string cornerStyleString) => enumStringService.Parse(cornerStyleString, CornerStyleOption.Round); public ColorSchemeOption GetColorScheme(string colorSchemeString) => enumStringService.Parse(colorSchemeString, ColorSchemeOption.TransparentDark); }Each of the classes in this example are small, usually only setting one css property to minimize collisions across classes.
Defining the styles themselves is outside the scope of this series, but you can find their definitions in the ~/scss directory, where .scss styles are defined and later compiled to css.
If you are using the main branch of the Training guides repository to follow along, the classes should already exist. - Remember to register the service with the dependency injection container in the TrainingGuides.Web/ServiceCollectionExtensions.cs file:
C#ServiceCollectionExtensions.cs
... public static void AddTrainingGuidesServices(this IServiceCollection services) { ... services.AddSingleton<IComponentStyleEnumService, ComponentStyleEnumService>(); ... } ...
Apply the styles with a tag helper
- Add a new class called
ComponentStyleTagHelperto the ~/Features/Shared/Helpers/TagHelpers folder. - Inherit from the .NET
TagHelperclass, and decorate it with theHtmlTargetElementattribute to set its element name totg-component-style.C#ComponentStyleTagHelper.cs... [HtmlTargetElement("tg-component-style")] public class ComponentStyleTagHelper : TagHelper ... - Give the tag helper string properties called
ColorSchemeandCornerStyle, so that objects that store selections as strings, such as page template properties, can use them directly.C#ComponentStyleTagHelper.cs... public string ColorScheme { get; set; } = string.Empty; public string CornerStyle { get; set; } = string.Empty; ... - Use a constant to hold the type of tag, in case we need to add more complex logic for determining the tag name in the future.
C#ComponentStyleTagHelper.cs
... private const string DIV_TAG = "div"; ... - Acquire an
IComponentStyleEnumServiceobject through dependency injection.C#ComponentStyleTagHelper.cs... private readonly IComponentStyleEnumService componentStyleEnumService; public ComponentStyleTagHelper(IComponentStyleEnumService componentStyleEnumService) : base() { this.componentStyleEnumService = componentStyleEnumService; } ... - In the
Processmethod, use the service to retrieve styles for the output tag.C#ComponentStyleTagHelper.cs... public override void Process(TagHelperContext context, TagHelperOutput output) { output.TagName = DIV_TAG; List<string> cssClasses = []; var colorScheme = componentStyleEnumService.GetColorScheme(ColorScheme); cssClasses.AddRange(componentStyleEnumService.GetColorSchemeClasses(colorScheme)); var cornerStyle = componentStyleEnumService.GetCornerStyle(CornerStyle); cssClasses.AddRange(componentStyleEnumService.GetCornerStyleClasses(cornerStyle)); if (cssClasses.Count > 0) { foreach (string cssClass in cssClasses) { output.AddClass(cssClass, HtmlEncoder.Default); } } } ...
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using TrainingGuides.Web.Features.Shared.Services;
namespace TrainingGuides.Web.Features.Shared.Helpers.TagHelpers;
[HtmlTargetElement("tg-component-style")]
public class ComponentStyleTagHelper : TagHelper
{
public string ColorScheme { get; set; } = string.Empty;
public string CornerStyle { get; set; } = string.Empty;
private const string DIV_TAG = "div";
private readonly IComponentStyleEnumService componentStyleEnumService;
public ComponentStyleTagHelper(IComponentStyleEnumService componentStyleEnumService) : base()
{
this.componentStyleEnumService = componentStyleEnumService;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = DIV_TAG;
List<string> cssClasses = [];
var colorScheme = componentStyleEnumService.GetColorScheme(ColorScheme);
cssClasses.AddRange(componentStyleEnumService.GetColorSchemeClasses(colorScheme));
var cornerStyle = componentStyleEnumService.GetCornerStyle(CornerStyle);
cssClasses.AddRange(componentStyleEnumService.GetCornerStyleClasses(cornerStyle));
if (cssClasses.Count > 0)
{
foreach (string cssClass in cssClasses)
{
output.AddClass(cssClass, HtmlEncoder.Default);
}
}
}
}
Put the tag helper to use
The tag helper is now ready to be added to the view. If you return to ProductPagePageTemplate.cshtml you can wrap all of the existing markup with the new tag helper tag, and pass along the color scheme and corner style properties from the page template’s configuration.
@using TrainingGuides.Web.Features.Products.Models
@using TrainingGuides.Web.Features.Products
@model TemplateViewModel<ProductPagePageTemplateProperties>
@{
var templateModel = Model.GetTemplateModel<ProductPageViewModel>();
}
<tg-component-style color-scheme="@Model.Properties.ColorScheme" corner-style="@Model.Properties.CornerStyle">
<div>
<div>
<h3>@templateModel.NameHtml</h3>
<p>@templateModel.ShortDescriptionHtml</p>
</div>
<div>
<img src="@templateModel.Media.FirstOrDefault()?.FilePath" alt="@templateModel.Media.FirstOrDefault()?.Description"/>
</div>
</div>
</tg-component-style>
If you sign in to the Xperience administration interface, you’ll notice that you can now adjust the appearance of the box containing the product’s data by clicking the gear icon in the bottom left of the Page Builder pane.
However, if you set the template to use rounded corners and a colored background, you’ll see that the corners of the image do not change to match the style of the parent div. The image may also be an unreasonable size, depending on the file.

Create an image tag helper
- In the ~/Features/Shared/Helpers/TagHelpers folder, add a new self-closing TagHelper called
StyledImageTagHelperwith the tag nametg-styled-image.C#StyledImageTagHelper.cs... namespace TrainingGuides.Web.Features.Shared.Helpers.TagHelpers; [HtmlTargetElement("tg-styled-image", TagStructure = TagStructure.WithoutEndTag)] public class StyledImageTagHelper : TagHelper ... - Follow the same procedure as last time to set up properties and an
IComponentStyleEnumService, leaving out theColorSchemeproperty.C#StyledImageTagHelper.cs... namespace TrainingGuides.Web.Features.Shared.Helpers.TagHelpers; [HtmlTargetElement("tg-styled-image", TagStructure = TagStructure.WithoutEndTag)] public class StyledImageTagHelper : TagHelper { public string CornerStyle { get; set; } = string.Empty; private const string IMG_TAG = "img"; private readonly IComponentStyleEnumService componentStyleEnumService; public StyledImageTagHelper(IComponentStyleEnumService componentStyleEnumService) : base() { this.componentStyleEnumService = componentStyleEnumService; } ... - In the
Processmethod, set theTagModeof the rendered HTML tag toTagMode.SelfClosingand style the image according to the classes returned by the service.C#StyledImageTagHelper.cs... public override void Process(TagHelperContext context, TagHelperOutput output) { output.TagName = IMG_TAG; output.TagMode = TagMode.SelfClosing; List<string> cssClasses = []; var cornerStyle = componentStyleEnumService .GetCornerStyle(CornerStyle ?? string.Empty); cssClasses.AddRange(componentStyleEnumService .GetCornerStyleClasses(cornerStyle)); if (cssClasses.Count > 0) { foreach (string cssClass in cssClasses) { output.AddClass(cssClass, HtmlEncoder.Default); } } } ...
using Microsoft.AspNetCore.Mvc.TagHelpers;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Razor.TagHelpers;
using TrainingGuides.Web.Features.Shared.Services;
namespace TrainingGuides.Web.Features.Shared.Helpers.TagHelpers;
[HtmlTargetElement("tg-styled-image", TagStructure = TagStructure.WithoutEndTag)]
public class StyledImageTagHelper : TagHelper
{
public string CornerStyle { get; set; } = string.Empty;
private const string IMG_TAG = "img";
private readonly IComponentStyleEnumService componentStyleEnumService;
public StyledImageTagHelper(IComponentStyleEnumService componentStyleEnumService) : base()
{
this.componentStyleEnumService = componentStyleEnumService;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = IMG_TAG;
output.TagMode = TagMode.SelfClosing;
List<string> cssClasses = [];
var cornerStyle = componentStyleEnumService.GetCornerStyle(CornerStyle ?? string.Empty);
cssClasses.AddRange(componentStyleEnumService.GetCornerStyleClasses(cornerStyle));
if (cssClasses.Count > 0)
{
foreach (string cssClass in cssClasses)
{
output.AddClass(cssClass, HtmlEncoder.Default);
}
}
}
}
The tag helper can be used in the view like this:
<tg-styled-image src="@templateModel.Media.FirstOrDefault()?.FilePath"
alt="@templateModel.Media.FirstOrDefault()?.Description"
corner-style="@Model.Properties.CornerStyle"
class="c-product-img object-fit-cover" />