Module: Page Builder

7 of 18 Pages

Finish the structured data template

Comparing the current template to the mockup from the start of this series, you’ll notice that the Service features table is missing.

Mockup of a service detail page

Its data is already in the template model, so it should be fairly straightforward to render to the page.

However, if you inspect the ServiceFeature content type, you may notice that its value can come from multiple different places depending on the selected ServiceFeatureValueType.

Let’s make one last tag helper, so that we don’t need to clutter the view with conditionals.

Add a service feature value tag helper

Since this one is specifically related to Service features, and not general styling, we can put it in the ~/Features/FinancialServices/TagHelpers folder.

  1. Create a new self-closing tag helper called ServiceFeatureValueTagHelper, with the tag name tg-service-feature-value.
  2. Add a ServiceFeatureViewModel property and a constant for the span tag.
  3. In the Process method, determine the formated value depending on the provided ServiceFeatureValueType from the view model and render it.
C#
ServiceFeatureValueTagHelper.cs

using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Razor.TagHelpers;

using TrainingGuides.Web.Features.FinancialServices.Models;

namespace TrainingGuides.Web.Features.FinancialServices.TagHelpers;

/// <summary>
/// Formats Service feature value based on its type.
/// </summary>
//This TagStructure allows the helper to be called with a single self-closing tag in razor views.
[HtmlTargetElement("tg-service-feature-value", TagStructure = TagStructure.WithoutEndTag)]
public class ServiceFeatureValueTagHelper : TagHelper
{
    public ServiceFeatureViewModel? Feature { get; set; }

    private const string SPAN_TAG = "span";

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = SPAN_TAG;
        // Make sure to set the output's  TagMode to StartTagAndEndTag.
        // This ensures that even though the tag helper is called with a single tag,
        // the rendered output closes the `span` tag that wraps the value.
        output.TagMode = TagMode.StartTagAndEndTag;

        string? formattedValue = Feature?.ValueType switch
        {
            ServiceFeatureValueType.Text => Feature.ValueHtml.Value,
            ServiceFeatureValueType.Number => "$" + Feature.Price.ToString("n2"),
            ServiceFeatureValueType.Boolean => Feature.FeatureIncluded ? "✔" : "-",
            _ => string.Empty
        };

        output.Content.SetHtmlContent(new HtmlString(formattedValue));
    }
}

Add the service features list to the template view

  1. In the ~/Features/FinancialServices/ServicePagePageTemplate.cshtml folder, add a foreach loop to cycle through the service features.
  2. Retrieve each feature’s LabelHtml directly, and use the new tag helper to display its value
  3. Wrap the whole thing in the tg-component-style tag helper, setting color-scheme directly and corner-style based on the template properties.
cshtml
ServicePagePageTemplate.cshtml

...
<tg-component-style color-scheme="@ColorSchemeOption.Light1" corner-style="@Model.Properties.CornerStyle"  class="c-table">
    @foreach (var feature in templateModel.Features)
    {
        <div>
            <div>@feature.LabelHtml</div>
            <div>
                <tg-service-feature-value feature="@feature"/>
            </div>
        </div>
    }
</tg-component-style>
...

Finish styling the template.

Now if you run the site, you’ll see all the data, but it’s still not formatted in a way that matches the mockup.

Screenshot of unfinished template

Add CSS styles and any necessary elements to make the template more visually appealing.

cshtml
ServicePagePageTemplate.cshtml

@using TrainingGuides.Web.Features.FinancialServices.Models
@using TrainingGuides.Web.Features.FinancialServices
@using TrainingGuides.Web.Features.Shared.OptionProviders.ColorScheme

@model TemplateViewModel<ServicePagePageTemplateProperties>

@{
    var templateModel = Model.GetTemplateModel<ServicePageViewModel>();
}

<tg-component-style color-scheme="@Model.Properties.ColorScheme" corner-style="@Model.Properties.CornerStyle">
    <div>
        <div>
            <h3>@templateModel.NameHtml</h3>
            <p>@templateModel.ShortDescriptionHtml</p>
        </div>
        
        @foreach (var asset in templateModel.Media)
        {
            <tg-styled-image src="@asset.FilePath" 
                alt="@asset.Description" 
                corner-style="@Model.Properties.CornerStyle" 
                class="c-product-img object-fit-cover" />
        }

        <tg-component-style color-scheme="@ColorSchemeOption.Light1" corner-style="@Model.Properties.CornerStyle"  class="c-table">
            @foreach (var feature in templateModel.Features)
            {
                <div>
                    <div>@feature.LabelHtml</div>
                    <div>
                        <tg-service-feature-value feature="@feature"/>
                    </div>
                </div>
            }
        </tg-component-style>
    </div>
</tg-component-style>

At the end of it all, you should be able to achieve this by configuring your template.

Screenshot of unfinished, but nicely formatted template