Kentico Xperience Design Patterns: MVC is Dead, Long Live PTVC
Tags: xperience, kentico, aspnetcore, csharp
Published: 2021-03-22 15:56:46

---
title: Kentico Xperience Design Patterns: MVC is Dead, Long Live PTVC
published: true
description: Learn about replacing the MVC design pattern with the pattern of combining Page Templates and View Components to maintain a separation of Content and Design
tags: xperience, kentico, aspnetcore, csharp
series: Kentico Xperience Design Patterns
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/hcusgcjdsp4aozej50g9.jpg
---

I've written previously about [the best ways to use Kentico Xperience's Page Builder](https://dev.to/seangwright/kentico-12-design-patterns-part-21-mvc-widget-tips-3img#widgets-vs-structured-content). We _should_ be using the powerful Page Builder as the place in an Xperience site where we combine raw structured content with design.

Now, I'd like to introduce a pattern I'm calling **Page Templates + View Components (PTVC)**, explain how it differs from the standard MVC pattern for Kentico Xperience, and detail the benefits it brings.

## 📚 What Will We Learn?

- Importance of [content separate from design](#keeping-content-separate-from-design)
- Customizing design [using the MVC pattern](#building-pages-with-mvc)
- Differences between [MVC and Page Templates](#building-pages-with-page-templates)
- How Page Templates help us [keep design out of our Content](#configure-design-in-page-template-properties)
- Why combine [Page Templates with View Components](#page-templates-and-view-components)
- [Benefits of PTVC vs MVC](#benefits-of-ptvc-over-mvc)

## 🧱 <a href="#keeping-content-separate-from-design" name="keeping-content-separate-from-design">Keeping Content Separate From Design </a>

I'm a big fan of keeping content separated from design 👍🏽. In fact, I presented an entire talk about this topic at Kentico Xperience Connection 2020, called [Content Driven Development: Or, Why the WYSIWYG Can Be a Trap](https://www.youtube.com/watch?v=b7cYeIL1ZcU).

Content that is design agnostic is more flexible and reusable. A simple example of this principle can be seen by looking at two example Page Type fields:

- `Title` - just text, no HTML
- `TitleHTML` - text and HTML (maybe even some inline styles)

Now, imagine we want to display these fields in many different places on a site - navigation, call-to-actions, page headings, image `alt` text, inside the `<title>` element in the `<head>`...

Which one is going to be easier to correctly use? The `Title` field, with no HTML, focusing only on content and no design, is always going to be easier to use 🙂.

Of course, we're always going to need fields like `TitleHTML` in our sites, because we want to give content managers the ability to format and apply design to content in very specific ways. But we should remember that this comes at the cost 💰 of reusability and flexibility.

## 🏗 <a href="#building-pages-with-mvc" name="building-pages-with-mvc">Building Pages with MVC</a>

If we look at the Kentico Xperience documentation on [application routing](https://docs.xperience.io/developing-websites/implementing-routing/content-tree-based-routing/setting-up-content-tree-based-routing) we can see there are two ways we can integrate it into our site.

> The rest of this post assumes we are using Content Tree based routing, but even if we are using custom routing, many of the benefits of the PTVC pattern still apply.

The first option is basic routing, which we can enable by creating a Razor View in a specific location in our project and naming it to match the the Page Type we want it to render. This approach, as we will see 🧐, is very similar to to the PTVC pattern.

The other option is advanced routing, which requires creating an ASP.NET Controller class and registering it with an `[assembly: RegisterPageRoute()]` attribute, and passing data to a Razor View with a View Model class. This is the approach I'm assuming most Xperience developers are using because it's the classic MVC pattern.

### 🎨 Customizing Design with MVC Pages

A question we should be asking ourselves is this:

**With advanced routing using MVC, where do we configure the design variations of our Pages?**

We could make the entire Page's Razor View an `<editable-area />`:

```html
<!-- ~/Views/Home/Index.cshtml -->

<editable-area />
```

This would let us follow the recommendation I made earlier and use the Page Builder as the place where [we combine structured content and design](https://dev.to/seangwright/kentico-12-design-patterns-part-21-mvc-widget-tips-3img#widgets-vs-structured-content) 😀, but that means each Page needs to be built and managed with the Page Builder.

What if there are thousands of Pages of a specific Page Type in a site? The Content Managers aren't going to appreciate having to go to _every single page_ to update a Widget or design change 😒.

What if we don't want to let the _structure_ of the Page to be customized but we do want the _design_ customized? The Page Builder doesn't support this, which means for these kinds of Pages, we need some other place to store all of our design related configuration for a Page 🤔.

The most common approach I see is putting this configuration in the Page Type fields - but this, unfortunately, is combining structured content and design, even if the design and content are in separate fields 😞.

As an example, should a Page Type 'design' field `HeadingColor` be used across the whole site? In lists? In the Page's 'details' View? What if there are 5 different places on a site which aren't using the Page Builder and have different design variations for the same content. How many Page Type fields do we add to represent these variations 😵?

My point is, *design is usually dependent on where content is presented, not where it comes from*, but *Page Types/Pages define the structure and source of content, not the display*.

## 🏗 <a href="#building-pages-with-page-templates" name="building-pages-with-page-templates">Building Pages with Page Templates</a>

If we look at Xperience's documentation on Page Templates, we can see that for basic routes (when using Content Tree based routing) there's not much we need to do:

> On sites using content tree-based routing: Basic routes handle everything for you. The system automatically initializes the data context of the page, the page builder feature, and displays the page template used by the page. - [Xperience Docs](https://docs.xperience.io/developing-websites/developing-xperience-applications-using-asp-net-core/page-builder-development-in-asp-net-core/developing-page-templates-in-asp-net-core#DevelopingpagetemplatesinASP.NETCore-Implementingpagetemplates)

Awesome! So, what does implementing a Page Template for a Page Type look like?

### Registration

We start with the `[assembly: RegisterPageTemplate()]` attribute which tells Xperience about our Page Template:

```csharp
[assembly: RegisterPageTemplate(
    identifier: "Sandbox.HomePage_Default", 
    name: "Home Page (Default)", 
    propertiesType: typeof(HomePageTemplateProperties), 
    customViewName: "~/Features/Home/Sandbox.HomePage_Default.cshtml")]
```

### Properties

The `propertiesType` is optional, but to follow the PTVC pattern, we're always going to create one (even if it's empty) 👍🏼, so lets create that now including the `HeadingColor` design option we mentioned previously:

```csharp
public class HomePageTemplateProperties
{
    [EditingComponent(
        DropDownComponent.IDENTIFIER,
        DefaultValue = "XperienceOrange",
        ExplanationText = "Sets the color of the Heading text",
        Label = "Heading Color",
        Order = 0)]
    [EditingComponentProperty(
        nameof(DropDownProperties.DataSource), 
        "Light\r\nDark\r\nXperienceOrange")]
    public string HeadingColor { get; set; } = "XperienceOrange";
}
```

### Filter

We'll also have a [Page Template Filter](https://docs.xperience.io/developing-websites/page-builder-development/developing-page-templates/filtering-page-templates) that associates a given request to a Page Template, based on the request's Culture, Page Type, and Parent Page (lot's of power and flexibility here, if it's needed).

I find that for most use cases, when implementing PTVC, these classes are pretty simple, to the point where a base class can abstract away most of the logic:

```csharp
[assembly: RegisterPageTemplate(
    "Sandbox.HomePage_Default",
    "Home Page (Default)",
    typeof(HomePageTemplateProperties),
    "~/Features/Home/Sandbox.HomePage_Default.cshtml")]

public class HomePageTemplateFilter : PageTypePageTemplateFilter
{
    public override string PageTypeClassName =>
        HomePage.CLASS_NAME;
}
```

> You can find the code for `PageTypePageTemplateFilter` in the [XperienceCommunity.PageTemplateUtilities](https://github.com/wiredviews/xperience-page-template-utilities) package, which helps simplify Page Template filter creation and registration 🤓.

### View

Finally, we create our Razor View (which can be placed anywhere since we can specify the path when registering it - I've always preferred a [feature folder](https://devnet.kentico.com/articles/enabling-feature-folders-in-xperience-13-mvc-net-core) based project structure):

```html
<!-- ~/Features/Home/Sandbox.HomePage_Default.cshtml -->

@using Sandbox.Features.Home
@using CMS.DocumentEngine.Types.HEN05 

@model ComponentViewModel<HomePageTemplateProperties>

<!-- ... -->
```

In the above code snippets, there are no custom Model or Controller classes. Instead, we have:

- Registration assembly attribute (required)
- Page Template properties (optional)
- Page Template Filter (option)
- Razor View (required)

## 🎨 <a href="#configure-design-in-page-template-properties" name="configure-design-in-page-template-properties">Configure Design in Page Template Properties</a>

Notice how we added the `HeadingColor` design option to our Page Template properties... 😮

This is a great solution for keeping content and design separate and the **primary reason** for adopting the PTVC pattern 😄!

We can have all of our structured content (eg. Home Page) in Page Type fields, and enable design customizations for each Page in the Page Template properties.

These two sets of data come together in the View:

```html
@using Sandbox.Features.Home
@using CMS.DocumentEngine.Types.HEN05 

@model ComponentViewModel<HomePageTemplateProperties>

@{
    if (Model.Page is not HomePage homePage)
    {
        return;
    }
    
    string headingClass = Model.Properties.HeadingColor switch
    {
        "Light" => "text-white",
        "XperienceOrange" => "text-primary",
        "Dark" or _ => "text-dark",
    };
}

<h1 class="@headingClass">@homePage.Fields.Title</h1>

<!-- ... -->
```

### Content Management

We can extend the Page Template properties as we enable more  design customization for the Page and we can set sensible defaults so that content managers aren't required to configure every single Page 🙂.

If we start out with only a single Page Template, we don't need to select it when creating a new Page, so our content management workflow doesn't need to change for simple scenarios. It's really an added bonus that Page Templates let content managers select from a developer-curated collection of predefined layouts (Razor Views).

This is really powerful and could be great for a product catalog where we have three ways of displaying products - standard, featured, and accessory. Each would use the same content model (`ProductPage`), but different layouts (Razor Views) and design customizations (Page Template Properties).

In my opinion, the Xperience product team is playing ♟ [3D chess](https://youtu.be/gL5EJDRicJo) with this feature 🤯.

## 🖼 <a href="#page-templates-and-view-components" name="page-templates-and-view-components">Page Templates + View Components</a>

### The Page Template-only Code Smell

Looking at our example above, can you spot the code smell 👃?

It's not too stinky yet, but if we leave the code as-is, after a couple months of feature enhancements we are going to end up with business logic (or maybe database access) in our View. That will smell real bad 💩💩💩💩💩!

> If we find ourselves needing to inject services into our Views, as presented in the [Xperience documentation](https://docs.xperience.io/developing-websites/developing-xperience-applications-using-asp-net-core/page-builder-development-in-asp-net-core/developing-page-templates-in-asp-net-core#DevelopingpagetemplatesinASP.NETCore-Addingcustombusinesslogic), for anything more than some localization, we've stepped right into a big fresh code smell and we should question our every move 🐾!

This code smell is why we have Controllers and Models to begin with - they let us prepare data for a View in advance, so that the View is only concerned with presentation behavior.

### 🧹 View Component Cleanup 

Fortunately, ASP.NET Core provides us with View Components, the perfect tool to complete the PTVC pattern! View Components let us _lift_ our business logic or model preparation back up into C# and out of a View 💯.

The PTVC pattern will have us create new View Components any time the Page Template View's `ComponentViewModel<Properties>` doesn't give us what we need. The View Component will be directly tied to the given Page Template because it will accept as parameters both the `Model.Page` and `Model.Properties`.

Here's an example for our Home Page Template:

```csharp
public class HomePageViewComponent : ViewComponent
{
    private readonly IPageRetriever pageRetriever;
    private readonly IPageUrlRetriever urlRetriever;

    public HomePageViewComponent(
        IPageRetriever pageRetriever,
        IPageUrlRetriever urlRetriever)
    {
        this.pageRetriever = pageRetriever;
        this.urlRetriever = urlRetriever;
    }

    public async Task<IViewComponentResult> InvokeAsync(
        HomePage page,
        HomePageTemplateProperties props)
    {
        // Get other data related to the home page
        var relatedPage = await retriever.Retrieve<TreeNode>(...);

        var url = urlRetriever.Retrieve(relatedPage);

        var model = new HomePageViewModel(
            page, props, relatedPage, url);

        return View(
            "~/Features/Home/Components/_HomePage.cshtml", 
            model);
    }
}
```

The `HomePageViewComponent` serves the role of the Controller we would have been using with the MVC approach, and our `HomePageViewModel`, as seen below, serves the same role as the MVC Model (View Components are an implementation of the MVC pattern):

```csharp
public class HomePageViewModel
{
    public HomePageViewModel(
        HomePage homePage,
        HomePageTemplateProperties props,
        TreeNode relatedPage,
        PageUrl relatedPageURL)
    {
        Title = homePage.Fields.Title;
        TitleCSSClass = Model.Properties.HeadingColor switch
        {
            "Light" => "text-white",
            "XperienceOrange" => "text-primary",
            "Dark" or _ => "text-dark",
        };
        RelatedPageTitle = relatedPage.Fields.Title;
        RelatedPageURL = relatedPageURL.RelativePath;
    }

    public string Title { get; }
    public string RelatedPageTitle { get; }
    public string RelatedPageURL { get; }
    public string TitleCSSClass { get; }
}
```

Now, our View Component View will look like a traditional MVC View:

```html
@model Sandbox.Features.Home.Components.HomePageViewModel

<h1 class="@Model.TitleCSSClass ">
    @Model.Title
</h1>

<p>Related: 
    <a href="@Model.RelatedPageURL">
        @Model.RelatedPageTitle
    </a>
</p>

<!-- ... -->
```

And, with that, we've cleaned up our Page Template, separated content and design, and covered all the parts of the PTVC pattern 🥳🎊!

## 💰 <a href="#benefits-of-ptvc-over-mvc" name="benefits-of-ptvc-over-mvc">Benefits of PTVC over MVC</a>

Let's quickly review the benefits that PTVC brings when compared to the traditional MVC approach:

#### 🧱 PTVC enables separating content from design

The separation of content and design is key to a maintanable Kentico Xperience site with flexbile, reusable content that can be easily validated for quality.

✔ PTVC lets us maintain this separation while still enabling design customization with its Page Template Properties.

❌ MVC forces us to maintain design settings in Page Type fields when not using the Page Builder.

#### 🛣 PTVC encourages using Basic Content Routing

✔ Basic Content Routing should be ideal for most (if not all) Pages in our site. Let Xperience handle mapping a URL to a `TreeNode` instance.

❌ MVC gives more control over routing at the cost of more complexity. Why add complexity when it's not needed?

#### 👶 PTVC is simple for simple cases

✔ If your Page relies entirely on the Page Builder, your Page Template View could be empty except for a `<editable-area ... />`. A single Page Template Filter class can handle multiple Templates.

❌ MVC always requires a Controller and a Model.

#### 💪🏾 PTVC grows with the requirements

✔ PTVC isn't restricted to simple use cases. By adopting View Components to gather and transform our content, we can continue to encapsulate data access and presentation logic _outside_ of our Razor Views.

Without [child actions](https://www.davepaquette.com/archive/2016/01/02/goodbye-child-actions-hello-view-components.aspx) in ASP.NET Core, we are encouraged to use View Components anyway, so traditional MVC approaches will have us creating them as well. 

❌ This means with MVC we'll have MVC Controllers and View Models + View Component classes and View Models.

#### 🖼 PTVC enables future customization

✔ We can customize View layouts and design by adding more properties to a Page Template Properties class, or adding a new Page Template View for a given Page Type.

For example, one Page Template could have a pre-defined layout with only customizations for text or background colors, while another Page Template (for the same Page Type) could rely entirely on an `<editable-area />`, giving content managers full control when they need it!

❌ This kind of flexibility isn't possible with MVC without pushing design content into Page Type fields and building some logic to switch the Controller's View based on a Page Type field - this would need to be repeated for _every_ Page Type that needed this flexibility.

With Page Templates, we get this for free.

#### 💵 PTVC is easy to cache

✔ With the release of Kentico Xperience 13 Refresh 1 we can set [cache dependencies](https://docs.xperience.io/developing-websites/developing-xperience-applications-using-asp-net-core/caching-page-output-in-asp-net-core-applications#CachingpageoutputinASP.NETCoreapplications-Controllingcachedependencyinjection) on ASP.NET Core's `<cache>` Tag Helper.

By wrapping our View Component in the `<cache>` Tag Helper and setting the correct cache dependencies, we can effectively cache the entire body of our Page!

❌ With MVC, the Controller will _always_ execute and we'll always have to create a View Model, even if we wrap parts of the View in the `<cache>` Tag Helper.

## 🤓 Conclusion

Keeping a clear separation between structured content and design makes our content more reusable and enables applying design to that content contextually - the right design in the right place at the right time 🧠.

Building Pages in Kentico Xperience with MVC components lacks a place for us to store design related options for a Page when not using the Page Builder (which is the ideal place to combine structured content and design).

Page Templates with properties, which have the same functionality as Widget or Section properties, give us the ability to associate design customizations at the Page level, aligning with the separation of content and design.

If we combine Page Templates with View Components, we gain the content/design separation benefits _and_ keep our business logic and data access out of the View 👏🏾.

My recommendation is to ditch MVC (unless you need it for custom routing), and **build every Page following the PTVC pattern**, even when there's only a single Page Template and no design customizations in the properties. Starting with this pattern as a foundation opens up the possibility for future flexibility when it's needed, at little upfront cost.

What are your thoughts on MVC vs PTVC? Have you discovered other ways to keep a separation between content and design, or business logic and presentation?

Let me know in the comments below...

And, as always, thanks for reading 🙏!

## References

- [Kentico 12 Design Patterns: MVC Widget Tips](https://dev.to/seangwright/kentico-12-design-patterns-part-21-mvc-widget-tips-3img#widgets-vs-structured-content)
- [Content Driven Development: Or, Why the WYSIWYG Can Be a Trap](https://www.youtube.com/watch?v=b7cYeIL1ZcU) (YouTube video)
- [Implementing Content Tree based Routing](https://docs.xperience.io/developing-websites/implementing-routing/content-tree-based-routing/setting-up-content-tree-based-routing) (Xperience docs)
- [Implementing Page Templates](https://docs.xperience.io/developing-websites/developing-xperience-applications-using-asp-net-core/page-builder-development-in-asp-net-core/developing-page-templates-in-asp-net-core#DevelopingpagetemplatesinASP.NETCore-Implementingpagetemplates) (Xperience docs)
- [Filtering Page Templates](https://docs.xperience.io/developing-websites/page-builder-development/developing-page-templates/filtering-page-templates) (Xperience docs)
- [Enabling Feature Folders in Xperience 13](https://devnet.kentico.com/articles/enabling-feature-folders-in-xperience-13-mvc-net-core)
- [Xperience Page Template Utilities library](https://github.com/wiredviews/xperience-page-template-utilities) (GitHub)
- [Caching Page Output](https://docs.xperience.io/developing-websites/developing-xperience-applications-using-asp-net-core/caching-page-output-in-asp-net-core-applications#CachingpageoutputinASP.NETCoreapplications-Cachingpageoutput) (Xperience docs)

---

<figcaption>Photo by <a href="https://unsplash.com/@jordanmadrid">Jordan Madrid
</a> on <a href="https://unsplash.com">Unsplash</a></figcaption>

---

We've put together a list over on [Kentico's GitHub account](https://github.com/Kentico/Home/blob/master/RESOURCES.md) of developer resources. Go check it out!

If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.

{% tag kentico %}

{% tag xperience %}

Or my [Kentico Xperience blog series](https://dev.to/seangwright/series), like:

- [Kentico Xperience Xplorations](https://dev.to/seangwright/series/8185)
- [Kentico Xperience MVC Widget Experiments](https://dev.to/seangwright/series/9483)
- [Bits of Xperience](https://dev.to/seangwright/series/8740)