Petal

Structured rich text rendering in MVC

The ability to create rich texts and compose content with content modules is one of the core features of Kontent.ai as a headless CMS. ASP.NET MVC is a state-of-the-art open-source web framework. How do rich text, modular content, and MVC play together?

Avatar fallback - no photo

Jan LenochPublished on Nov 30, 2017

Perfectly. MVC web apps are capable of rendering an arbitrary hierarchy of content modules placed into the rich text. Developers no longer have to assist each time content editors do their mix-and-matching of content modules. Let’s find out how to add such flexibility to your MVC apps.

Use cases for modular content

Let’s start by recapping all you can do with modular content:

  • Reuse parts of a content item across the app
  • Split larger content items into smaller parts
  • Allow for a hierarchical structure of a page (e.g. collapsible parts)
  • Enforce a specific format on a certain part of a content item
  • Assign various contributors or translators to parts of a content item, etc.

There are two ways to work with modular content in Kentico Cloud:

Adding content modules to a modular content element

This way, content modules reside in their dedicated content element.

Kontent.ai


How to work with modular content in MVCwlement

This way, content modules reside inside a rich text element, together with the text.

Kontent.ai


Now, how is it that MVC allows such freedom with content modules? Let’s see. First, I’ll explain the basics of a nice feature in MVC called “display templates”. Then, I’ll talk through the basic steps for adjusting an existing MVC app. Finally; I’ll do a quick demo using our Dancing Goat sample site.

How to Work with Modular Content in MVC

There are basically two ways. One that’s more traditional, and one that is more flexible.

The traditional way is to render parts of the view model that are known at design time. For an example in our Dancing Goat app, I’d refer to ~/Views/Home/Index.cshtml. The below code block works with the Article type known at design time.

@foreach (DancingGoat.Models.Article article in Model.ContentItem.Articles)

Using the flexible method of rendering means doing it through display templates. With templates, the MVC view does not have to know the types of all the inner parts of the view model. All you have to do in your view is to call the @Html.DisplayFor method. For a clear example, I’ll refer to a default view of a simple app created in one of my previous blog posts.

@model PageViewModel

@section Navigation
{
    @Html.Partial("_DrawerMenuPartial", Model.Navigation)
}

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

Note that the type of Model.Body isn’t relevant to this view.

Which way is better?

If you have all your content modules in modular content elements (as in the “Home” content item in the screenshot above) and if they’re in a flat structure, you can rely on the traditional way.

But, if you wish to have deep structures or if you want to use your content modules in rich text elements (as in the “Coffee Beverages Explained” item depicted above), the only way is to leverage display templates.

How Display Templates Work

Templates are basically HTML snippets dedicated to a CLR type. They have the same format as MVC views (.cshtml). Unlike views, which are handpicked by developers during design, templates are used for rendering automatically by MVC, during run time. Remember, you only call the DisplayFor method for a model property, without saying what type the model has and what it is composed of. The model may contain a collection (or even a deep hierarchy) of other content items of types that aren’t known at design time.

For each item in the collection (hierarchy), MVC picks the right template according to a naming convention. By default, MVC searches for .cshtml files that have the names of the CLR types in the view model. MVC searches for display templates in ~/Views/Shared/DisplayTemplates/ or ~/Views/[controller name]/DisplayTemplates/. Instead of allowing MVC to pick the right template, you can also invoke a specific template explicitly, via an optional parameter of the DisplayFor method.

How to Use Templates

(Spoiler alert: It is super easy. Just create display template files for types and rely on calling @Html.DisplayFor.)

First, create the [model name].cshtml files in the ~/Views/Shared/DisplayTemplates/ or ~/Views/[controller name]/DisplayTemplates/ locations. Create files for all types that you expect to be used for content modules.

To allow rendering of modules in rich text, generate your types with the model generator and use the --structuredmodel parameter. That way, all content elements of type “Rich text” will be generated in the form of IRichTextContent properties, instead of just string.

Upon a request for content, our Delivery SDK fills each IRichTextContent property with a collection of string blocks (HTML headings, paragraphs etc.) and content module objects (i.e. nested content items). The content modules are strongly typed, so MVC can automatically pick a proper template for each of them.

From now on, every time you call “@Html.DisplayFor(vm => vm.[some rich text property])” in your view, the rich text will contain HTML from the inner content modules.

Templates vs. Views

I’d like to give you some recommendations on when to use what:

  • When items of a certain type need to be rendered in a very specific way in a specific part of your app, put your HTML into the regular MVC view.
  • If you need to use the HTML in multiple parts of your app, but you don’t use the type for content modules, it is fine to have the HTML in partial view.
  • If you need to use the HTML in multiple parts of your app and you use the type for content modules, put the HTML into a display template.
  • If you wish to leverage the flexibility of display templates and you want your items to be rendered in various renditions (like listing entries, search results, detailed views, mobile views, etc.), create additional display template files with suffixes in their names and call them via custom HTML helper methods as per my forum post.

Quick Demo

I’ll use our traditional Dancing Goat MVC 5 sample site. First, I’ll describe an already existing example of content modules in rich text. But, because the example does not demonstrate deep hierarchies, I’ll create another small example of it.

Rich Text

In the second screenshot in this article, you may have noticed that we had already added a few content modules to some rich text elements in our Dancing Goat sample site. In two articles we added tweets and a hosted (embedded) video. I’ll quickly describe the implementation of the hosted video that resides in the “Which brewing fits you?” article.

To allow user-friendly adding of videos, we first created a “Hosted video” content type.

Kontent.ai


Then, we created an item and added it as a content module into the above article. On the live site, it is presented as an embedded video in the article text.

Kontent.ai


Then, we re-generated the model and created the ~/Views/Articles/DisplayTemplates/HostedVideo.cshtml file.

@model DancingGoat.Models.HostedVideo

@{ 
    var host = Model.VideoHost.FirstOrDefault()?.Codename;
    if (host == "vimeo") {
        <iframe class="hosted-video__wrapper"
                src="https://player.vimeo.com/video/@(Model.VideoId)?title =0&byline =0&portrait =0"
                width="640"
                height="360"
                frameborder="0"
                webkitallowfullscreen
                mozallowfullscreen
                allowfullscreen
                >
        </iframe>
    }
    else if (host == "youtube") {
        <iframe class="hosted-video__wrapper"
                width="560"
                height="315"
                src="https://www.youtube.com/embed/@(Model.VideoId)"
                frameborder="0"
                allowfullscreen
                >
        </iframe>
    }
}


That was it.

Deep Hierarchy

Now, let’s quickly demonstrate rendering of a hierarchy of content modules.

In short, I’ll add a new “Related Products” section to the end of each article (lime dashed line). In each product, I’ll be able to put nested content items of type Accessory (red dashed line) to each product. The accessories will be listed in parentheses in the related product’s description. The result will look like this:

Kontent.ai


The following hierarchy of content modules will be created.

  • Which brewing fits you? (type Article)
    • AeroPress (type Brewer)
      • AeroPress Filters (type Accessory)
    • Hario V60 (type Brewer)
Kontent.ai
Kontent.ai


In the code of the MVC app, I’ll do the following.

In ~/Controllers/ArticleController.cs, I’ll raise the DepthParameter to “2”.

var response = await client.GetItemsAsync<Article>(
	new EqualsFilter("elements.url_pattern", urlSlug),
	new EqualsFilter("system.type", "article"),
	new DepthParameter(2)
);


I’ll re-generate the models (Article.cs and Brewer.cs), or add the new properties manually.

public IEnumerable<object> RelatedProducts { get; set; }
public IEnumerable<object> RecommendedAccessories { get; set; }


To the end of the contents of the ~/Views/Articles/Show.cshtml file, I’ll add some code to render the “Related Products” section.

@if (Model.RelatedProducts.Any())
{
    <div class="article-related-articles">
        <h1 class="title-tab">@Resources.DancingGoat.Articles_RelatedProducts</h1>
        <div class="row">
            @Html.DisplayFor(vm => vm.RelatedProducts)
        </div>
    </div>
}


Then, I’ll just create two display templates (in ~/Views/Articles/DisplayTemplates/):  Brewer.cshtml and Accessory.cshtml

@model DancingGoat.Models.Brewer

<div class="col-lg-3 col-md-6 col-sm-12">
    <div class="article-tile">
        <a href="@Url.Action("Detail", "Product", new { urlslug = Model.UrlPattern })">
            @Html.AssetImage(Model.ProductImage.FirstOrDefault(), title: "Brewer " + Model.ProductProductName, cssClass: "article-tile-image")
        </a>
        <div class="article-tile-content">
            <h2 class="h4">
                <a href="@Url.Action("Detail", "Product", new { urlslug = Model.UrlPattern})">@Html.DisplayFor(vm => vm.ProductProductName)</a>
            </h2>
            <p class="article-tile-text">
                @Html.DisplayFor(vm => vm.ProductShortDescription)
            </p>
            @{
                if (Model.RecommendedAccessories.Any())
                {
                    <p>
                        (Accessories:
                            @foreach (var accessory in Model.RecommendedAccessories)
                            {
                                @Html.DisplayFor(vm => accessory);
                                if (!accessory.Equals(Model.RecommendedAccessories.Last()))
                                {
                                    @Html.Raw(", ")
                                }
                            }
                        )
                    </p>
                }
            }
        </div>
    </div>
</div>
@model DancingGoat.Models.Accessory

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


As a result, the app renders the nested content no matter what type it is. If I had created templates for Coffee and Grinder types, I’d be able to swap the "AeroPress" and "Hario V60" with any other product items (with whatever accessories were contained within them). I’d be able to swap things around and the app would happily render the content for me, without any further changes to the views or re-deployments.

Get the Code

As usual, you can get the code from a dedicated folder in the Kentico Cloud Code Examples repository. You may also want to look at the wiki for other details.

Wrapping Up

With just a few adjustments, the app became resistant to changes in page composition. If I were a developer of the Dancing Goat company, I would no longer be required to assist with each page re-structure.

If you like the concept of modular content and display templates, or if you have any questions or comments, please let us know! Either through the below chat, or in our forums.

Avatar fallback - no photo
Written by

Jan Lenoch

Feeling like your brand’s content is getting lost in the noise?

Listen to our new podcast for practical tips, tricks, and strategies to make your content shine. From AI’s magic touch to content management mastery and customer experience secrets, we’ll cover it all.

Listen now
Kontent Waves