DEV Community

Sean G. Wright
Sean G. Wright

Posted on

Kentico 12: Design Patterns Part 9 - The Different Ways to Store Content in Kentico 12 MVC

Photo by JΓ‘n Jakub NaniΕ‘ta on Unsplash

There are many different ways to store and display content in our Kentico 12 MVC applications - probably an infinite number! 🀯

I'd like to cover what I think are the most popular and useful patterns. I also want to give some example scenarios of when you might use these patterns and provide a pros and cons list for each.

We will start with more simplistic options and end with the newer, more complex, and flexible options. πŸ‘Œ

Content Storage Patterns

πŸ”Έ Hard-coded!! 🀘🎸πŸ”₯

We can put our content directly into variables or parameters in C# code or in our Razor view files. These could be words, dates, urls, or anything else.

Pros:

  • βœ… Definitely the easiest approach.
  • βœ… No database query needed.
  • βœ… Versioned by source control.

Cons:

  • ❌ Changes require a developer to deploy code updates.
  • ❌ Not localized for visitors using different languages.
  • ❌ No integration into document workflow requirements.
  • ❌ No guarantee that content is centralized - different values could be used all over the site.

I'd only recommend taking this approach for proof-of-concepts and mock-ups or for content that doesn't need localized and you know will very rarely change.

If we need to use Kentico in the first place then we won't have much content matching these requirements. πŸ‘

Example:

// ProductController.cs

public ActionResult Index()
{
    string title = "Product Listing";

    return View(new { Title = title });
}
Enter fullscreen mode Exit fullscreen mode
<!-- ~\Views\Product\Index.cshtml -->

<h1>@Model.Title</h1>
Enter fullscreen mode Exit fullscreen mode

πŸ”Έ Application Settings or Kentico Settings Module

We can use ConfigurationManager.AppSettings["key"] or SettingsKeyInfoProvider.GetValue("key") and display this however we want.

Pros:

  • βœ… Centralized storage for content.
  • βœ… Pretty easy to update (assuming it's in Kentico's settings table).
  • βœ… Not related to a specific Page in the content tree or a Page Type, which might make it more flexible.

Cons:

  • ❌ Not localizable as .NET and Kentico don't support it for these storage approaches.
  • ❌ Not intuitive as to what content displayed on a site is stored in settings.
  • ❌ Not a great editing experience for content editors.
  • ❌ No integration into document workflow requirements.
  • ❌ No versioning (if done in the CMS).

Generally I wouldn't recommend storing publicly displayed content in settings, but there might be a few cases (no Localization is required, the content is for technical use) where it could be useful.

Example:

// ProductController.cs

public ActionResult Index()
{
    string saleEndDateSettingValue = SettingsKeyInfoProvider.GetValue("Sale_End_Date");

    DateTime saleEndDate = DateTime.Parse(saleEndDateSettingValue);

    return View(new { SaleEndDate = saleEndDate });
}
Enter fullscreen mode Exit fullscreen mode
<!-- ~\Views\Product\Index.cshtml -->

<h3>The current sale ends on 
    <span class="highlight">@Model.SaleEndDate.ToShortDateString()</span>
</h3>
Enter fullscreen mode Exit fullscreen mode

Here is the Kentico Settings module where settings values can be stored.

Here is the node in the MVC site web.config file where settings can be stored.

πŸ”Έ Resource (.resx) File or Kentico Localization Module

.NET provides ways to store and read data from Resource files.

Kentico also provides quick access to resource strings through various methods

  • ResHelper.GetString("ResourceKey") in C#
  • @ResHelper.GetString("ResourceKey") in Razor views
  • "{$ResourceKey$}" in attributes

You can read more on using Resource strings in Kentico's documentation: Setting up a multilingual user interface

Pros:

  • βœ… Can be stored in the CMS so it's editable by content editors.
  • βœ… .NET provides mechanisms for localizing content so implementation is simpler.
  • βœ… Content is centralized which helps guarantee consistency.

Cons:

  • ❌ If stored in a Resource file, and not the CMS, it requires a developer to change it.
  • ❌ It's not always clear where the displayed value is configured.
  • ❌ No integration into document workflow requirements.
  • ❌ No versioning (if done in the CMS).

Resource files and the Localization module are great for infrequently changing content that needs to be translated or that shouldn't be hard-coded because it is re-used throughout our site.

Even if we are only writing content for one language, making sure "Sign In" doesn't become "Sign On", "Sign-In", "Login", or "Log On" throughout our site can make this pattern valuable. πŸ˜‰

Example:

<!-- Any .cshtml file -->

<button type="submit">@ResHelper.GetString("Sandbox.Products.AddToCart")</button>
Enter fullscreen mode Exit fullscreen mode

Here is the Kentico Locationlization module in the CMS where resource keys and values can be defined for different languages.

Here is a .resx file being edited in Visual Studio for centralized content / translation management for UI elements in the CMS.

πŸ”Έ Custom Table or Custom Module Class

Custom tables and Module classes are very similar to the next option we'll cover, custom Page Types, but without all the Document related functionality.

There is no content tree, no document versioning or relationships, just tables, rows, and APIs to access the data. πŸ€”

Pros:

  • βœ… Doesn't have the overhead of Pages / Documents, so it scales very wel.
  • βœ… Flexible and custom defined database schemas.
  • βœ… Strongly typed (custom Module classes).
  • βœ… Editable by content editors if site permissions allow it.
  • βœ… UX can be designed for an ideal editing experience (custom Module classes).

Cons:

  • ❌ No versioning or workflow.
  • ❌ No translation.
  • ❌ No obvious connection to specific Pages / Documents.
  • ❌ Not clear where the content might show up on the site.
  • ❌ Extra developer effort to make the content editing UX pleasant.

The main difference between custom tables and Module classes, from a developer's perspective, is the strong typing - custom Module classes have it, custom tables don't.

From the content editor's perspective it's going to the UI for managing the data. Custom tables provide a data grid management screen out-of-the-box, but it's pretty limited. Custom Module classes need their UI configured but are more powerful and flexible in this regard.

Custom tables are really best suited for bulk data, and while they are flexible they don't provide great developer ergonomics.

Custom Module classes allow developers to define a database table schema and also auto-generate data access code to retrieve, insert, update, and delete the data stored in that table in a strongly-typed way. ⚑

These classes also support powerful UI building functionality within the CMS to allow for entire new Kentico features, and the screens to manage them, to be created by developers.

The most common use case I've found for these options is when integrating external data that doesn't need to be stored in the documents in the content tree.

Example - Custom Tables:

// ProductController.cs

public ActionResult Detail(int productDataId)
{
    CustomTableItem item = CustomTableItemProvider
        .GetItem(productDataId, "Sandbox.ProductData");

    int inventory = item.GetValue<int>("WarehouseCount", 0);

    return View(new { Inventory = inventory });
}
Enter fullscreen mode Exit fullscreen mode
<!-- ~\Views\Product\Detail.cshtml -->

<h3>We only have @Model.Inventory items left in stock</h3>
Enter fullscreen mode Exit fullscreen mode

Example - Custom Module Class:

// ProductController.cs

public ActionResult Detail(int productDataId)
{
    WarehouseItem item = WarehouseItemProvider
        .GetWarehouseItem(productDataId);

    int inventory = item.Inventory;

    return View(new { Inventory = inventory });
}
Enter fullscreen mode Exit fullscreen mode
<!-- ~\Views\Product\Detail.cshtml -->

<h3>We only have @Model.Inventory items left in stock</h3>
Enter fullscreen mode Exit fullscreen mode

πŸ”Έ Custom Page Types

Creating custom Page Types with fields to store data is probably the most intuitive, flexible approach, and common approach.

This approach queries data for a specific page type instance from the database and passes its field values to a Razor view for rendering.

These page type instances make up some of the items stored in the CMS Pages module content tree.

Pros:

  • βœ… Kentico's content management functionality is built around this pattern.
  • βœ… Content associations are clearly linked to specific page instances in the content tree.
  • βœ… Retrieval is easy using Kentico's auto-generated custom Page Type code.
  • βœ… Content changes are subject to document workflow requirements.
  • βœ… Content can be centralized in one place (page) and re-used on many different views throughout the site.
  • βœ… Content editors can manage all the content themselves.

Cons:

  • ❌ Requires some architecture and planning since new classes need compiled into the MVC codebase, and Page Type field configuration needs thoughtful consideration.
  • ❌ All data is stored in the database, so performance can be an issue at scale without caching or optimized data access.
  • ❌ It can be hard to use Page Type fields to change design elements, and even if it works, the effects that changing values has on the page design are not intuitive to content managers.

Most of our Kentico content storage will be performed with custom Page Types and their fields.

The Page Type hierarchy restrictions and field form control selections can help developers ensure content stays well organized for the lifetime of a site. 🀠

Custom Page Types are the most similar content storage and display pattern to traditional Portal Engine techniques.

Example:

// Article.cs - the auto-generated custom Page Type code for an Article

public partial class Article : TreeNode
{
    [DatabaseField]
    public string ArticleTitle
    {
        get => ValidationHelper.GetString(GetValue("ArticleTitle"), @"");
        set => SetValue("ArticleTitle", value);
    }
}

// ArticleController.cs

public ActionResult Article(Guid nodeGuid)
{
    Article article = ArticleProvider
        .GetArticle(nodeGuid, "en-us", SiteContext.CurrentSiteName);

    return View(new { Title = article.ArticleTitle });
}
Enter fullscreen mode Exit fullscreen mode
<!-- ~\Views\Article\Index.cshtml -->

<h1>@Model.Title</h1>
Enter fullscreen mode Exit fullscreen mode

Here is the content form shown in the CMS Pages module for editing the value associated with the ArticleTitle field above.

πŸ”Έ PageBuilder functionality (Sections / Widgets)

PageBuilder is the newest Kentico feature for displaying content on our sites, is an MVC-only feature, and doesn't work with MVC implementations of Kentico before version 12.

Widgets and Sections allow content editors to store bits of content directly in the DocumentPageBuilderWidgets column of the CMS_Document table as serialized JSON. πŸ€“

Content editors work with a nice UI, developers work with a powerful API and Kentico handles wiring everything up in the CMS UI.

Pros:

  • βœ… Complex and responsive editing interfaces directly in the page content flow.
  • βœ… Widget UX and functionality can be completely customized by developers.
  • βœ… Easy to see how content changes effect the design and display of the page.
  • βœ… Content changes are subject to document workflow requirements.
  • βœ… Content editors can change design and layout without needing developer support.

Cons:

PageBuilder is the new hot fun Kentico technology featured in Kentico 12 MVC. 😎

Its goal is to provide some of the functionality that Portal Engine sites provided in previous versions of Kentico.

If the content editors need the ability to move content around in a layout then PageBuilder can be a good option.

Also if there are repeated UX patterns throughout the site (think, call-to-action buttons, hero images, two columns of text paragraphs) and these patterns need to be available to be mixed and matched without developer intervention, then PageBuilder can help meet these requirements.

That said, given the limitations listed in the Cons section above, there will be places where custom Page Types is definitely a better option (re-usable content being the primary one).

Example:

// From the "LearningKit" sample site

// NumberWidgetController.cs

public class NumberWidgetController : WidgetController<NumberWidgetProperties>
{
    // Default GET action used to retrieve the widget markup
    public ActionResult Index()
    {
        // Retrieves the properties as a strongly typed object
        NumberWidgetProperties properties = GetProperties();

        // Creates a new model and sets its value
        var model = new NumberWidgetViewModel
        {
            Number = properties.Number
        };

        return PartialView("Widgets/_NumberWidget", model);
    }
}
Enter fullscreen mode Exit fullscreen mode
<!-- ~\Views\Shared\Widgets\_NumberWidget.cshtml -->

@model ComponentViewModel<NumberWidgetProperties>

<h3 style="background-color: #dddddd;">
    The number you chose for today is: @Model.Properties.Number</h3>

@if (Context.Kentico().PageBuilder().EditMode)
{
    Html.RenderPartial("InlineEditors/_NumberEditor", new NumberEditorModel
    {
        PropertyName = nameof(NumberWidgetProperties.Number),
        Number = Model.Properties.Number
    });
}
Enter fullscreen mode Exit fullscreen mode

Here is the dialog shown in the CMS Pages module for editing the value displayed by the NumberWidget Partial View above.

Comparing Custom Page Type and PageBuilder Content

Kentico's documentation has a great page that can help us make the decision between using custom Page Type fields or PageBuilder functionality. 🧐

I think both options are flexible in what use cases they cover and both can be customized to some extent.

We can add fields to a custom Page Type that toggle design and layout related values in the Razor view.

Imagine a checkbox that toggles a border defined in CSS or a number that changes the font-size of a section header. πŸ€”

We can access the content from a field of the current custom Page Type, that a widget is being configured for, in PageBuilder components.

Imagine a PageBuilder section that has a custom Controller which returns a view model with the current page's DocumentName as a Title property. The section Razor view can then render that content somewhere in the section markup. πŸ€”

There is definitely overlap here and I think, as a community, we'll figure out where the comfortable limits are and maybe even develop some creative solutions for pushing those limits or combining the two patterns.

As a rule of thumb I'd say start with custom Page Types, for single or repeated content items, and enhance the content editing experience with PageBuilder functionality where the flexibility has been requested by the client.

If you are transitioning from a Portal Engine site running on a previous version of Kentico, then more PageBuilder functionality than normal might help ease the transition for content editors since it will "feel" a lot more like Portal Engine.

Summary

We looked at some of the many ways that content can be stored in a Kentico 12 MVC site and the various pros and cons of each.

  • Hard-coded
  • Stored in application settings or Kentico's Settings module
  • Stored in a Resource File or Kentico's Localization module
  • Using a Custom Table or custom Module classes
  • Using a custom Page Type and its fields
  • Using PageBuilder functionality with Sections and Widgets

The most common and useful of these patterns, for general content storage, are custom Page Types and PageBuilder components, but it's nice to have the other options available.

Kentico's documentation on content storage structure and options is a great place to read up and dig into details on everything covered here and more.

I have been working on interesting combinations and use-cases of these content storage patterns that I'd like to share in the future, but for now...

Thanks for reading! πŸ™


If you are looking for additional Kentico content, checkout the Kentico tag here on DEV:

#kentico

Or my Kentico blog series:

Top comments (0)