How To Render Different Output for Rich Text in Kentico Kontent using the Delivery .NET SDK

Code Kentico

Kentico Kontent rich text data can be handled in two different ways using the Delivery .NET SDK. This article briefly outlines the types of rich text, the options for displaying it, and then describes how you can render different output. 

Rich Text Content Types

Kentico Kontent rich text content elements can hold the following types:

Rendering Option One: Strings

When consuming the Delivery .NET SDK you should be building strongly typed models using the Kentico Kontent model generator utility for .NET. By default rich text elements have string properties on the generated models. These will automatically have markup for the HTML and images, which will be built using the ToString() implementation on the implementing classes (HtmlContent and InlineImage). To get markup for your content items or components you need to do a bit more work and generate a IInlineContentItemsResolver<T> class for the component type. So you have something like this:

public class BlockquoteResolver : IInlineContentItemsResolver<Blockquote>
{
    public string Resolve(Blockquote data)
    {
        return $"<blockquote>{data.Text}</blockquote>";
    }
}

This approach to rendering, outside the normal tools you have available in ASP.NET MVC is pretty limited. That is where the second approach comes in.

Rendering Option Two: IRichTextContent

If you use the --structuredmodel or -s parameter on the model generator your types will use the IRichTextContent interface for rich text elements. This interface inherits from IEnumerable<IRichTextBlock> and the types above are implemented by interfaces that inherit from this base interface: IHtmlContent, IInlineImage and IInlineContentItem. Our rich text is now nicely broken down into blocks. The IInlineContentItem class has an object ContentItem property that holds the actual item. This now allows you to simply render your output using display templates with DisplayFor. So you have something like this:

@Html.DisplayFor(vm => vm.Body)

ASP.NET MVC automatically enumerates the IEnumerable and for HTML and image blocks it takes care of the rendering by calling ToString() on the implementing types as for option one above. You are free to add display templates for these types to modify the default markup. To render a content item you simply create a display template for the underlying model type generated by the model generator. So the matching implementation for a block quote would be:

@model RobWest.Website.Models.Blockquote
@{
    <blockquote><p>@(Model.Text)</p></blockquote>
}

This approach is by far the more flexible one, and allows you to hook into the usual ASP.NET rendering approaches.

Conditional Rendering

This is all fantastic, but what if you want to have different output for one of these types of rich text. I came at this problem because I wanted to generate AMP versions for my articles. The specification requires that images are rendered in AMP using an amp-img tag.

The string approach can't deal with this because the resolver has no knowledge of its context, and so can't be told to change its output.

With  IRichTextContent, as the DisplayFor is applied to the collection there is no way to specify a template for the individual blocks.

If you think you can solve this by simply enumerating the blocks and calling DisplayFor on each one, you'll be disappointed. For some reason when doing this ASP.NET MVC does not have the right meta data to pick up the correct display template when it isn't specified. So if you try something like this:

@foreach (var block in Model.Body)
{
    @switch (block)
    {
        case Kentico.Kontent.Delivery.Abstractions.IInlineImage image:
            @Html.DisplayFor(vm => image, "AmpInlineImage")
            break;
        default:
            @Html.DisplayFor(vm => block)
            break;
    }
}

You'll find that your image works fine, but now no other content items will be resolved and displayed. HTML blocks will also get rendered using the default object template from ASP.NET MVC.

So how do you get around this? The way I have done it is to pass in additional view data to the DisplayFor call. This is then available to each display template and you can modify the display accordingly. In the view you do something like this:

@Html.DisplayFor(vm => vm.Body, new { isAmp = true })

and in your display template something like this:

@model Kentico.Kontent.Delivery.Abstractions.IInlineImage

@if (ViewData["isAmp"] is bool && (bool)ViewData["isAmp"])
{
    <amp-img src="@(Model.Src)" alt="@(Model.AltText)" width="@(Model.Width)" height="@(Model.Height)"></amp-img>
}
else
{
    <figure>
        <mg src="@(Model.Src)" alt="@(Model.AltText)" width="@(Model.Width)" height="@(Model.Height)">
        <figcaption>@(Model.AltText)</figcaption>
    </figure>

}

It isn't particularly elegant as it would be nice to separate out the two different renderings into different views, but it gets the job done.

As a side note, the width and height on IInlineImage are currently in a beta version if you are wondering why you don't see them in the public NuGet package right now. Petr Švihlík added these fields recently when I suggested it as a possible enhancement.

Conclusion

The structured model approach is by far the best way to approach rich content using the Delivery .NET SDK. However, the approach to rendering rich text content could do with some extension. It would be nice to have a way to handle the display template rendering neatly on a case by case basis.