Build a page template

This page is part of a series you should follow sequentially from beginning to end. Go to the first step.

We recommend using Page templates to display content in website projects. They enable flexibility in the presentation of a page without developer intervention – you can set up multiple templates for editors to choose from, and include properties that allow them to configure the design.

Now that we have created a Landing page content type and configured the project, let’s create a simple template to display landing pages. The template will access fields from the reusable Slogan item linked by the Landing page. 

In the Kickstart.Web project, create a directory Features, with a subfolder LandingPages for code files relating to landing pages.

Add a view model class

Let’s start by defining a view model class to hold the data the template should display.

Add a new class named LandingPageViewModel to the LandingPages folder, with a Message property to represent some text that should be displayed on the page.

Then add a GetViewModel method that takes a LandingPage as the parameter. It should use the SloganText of the linked Slogan item for the Message property.

C#
LandingPageViewModel.cs


using System.Linq;

namespace Kickstart.Web.Features.LandingPages;

public class LandingPageViewModel
{
    public string Message { get; set; } = string.Empty;

    public static LandingPageViewModel GetViewModel(LandingPage landingPage) =>
        new()
        {
            Message = landingPage?.LandingPageSlogan.FirstOrDefault()?.SloganText ?? string.Empty
        };
}

Define a page template controller

Page templates use a ComponentViewModel object as a model by default. This is useful for accessing any page template properties you may have set up, but adds an extra step to accessing content item data.

Let’s create a controller that allows you to substitute your own LandingPageViewModel instead, so that you can use it directly in the view.

Add the class

Create a new class called LandingPageController in the Features/LandingPages folder of Kickstart.Web.

Then add an asynchronous Index action for the controller, which returns a TemplateResult object.

C#
LandingPageController.cs


using System.Threading.Tasks;
using Kentico.PageBuilder.Web.Mvc.PageTemplates;
using Microsoft.AspNetCore.Mvc;

namespace Kickstart.Web.Features.LandingPages;
public class LandingPageController : Controller
{
    public async Task<IActionResult> Index()
    {
        return new TemplateResult();
    }
}

Query the landing page

To retrieve the data of a given landing page, you’ll need these key pieces:

  1. IWebPageDataContextRetriever to figure out which Landing page led to the controller.
  2. ContentItemQueryBuilder to define the parameters of the query.
  3. IWebSiteChannelContext to decide which website channel’s pages to pull from and determine whether unpublished content should be retrieved for preview mode.
  4. IPreferredLanguageRetriever to determine which language version of the content you need.
  5. IContentQueryExecutor to retrieve the data and map it to a LandingPage object in code.

Once you retrieve the landing page data, you can use the GetViewModel method from earlier in this step to create a LandingPageViewModel.

C#
LandingPageController.cs


using System.Linq;
using System.Threading.Tasks;
using CMS.ContentEngine;
using CMS.Websites;
using CMS.Websites.Routing;
using Kentico.Content.Web.Mvc;
using Kentico.Content.Web.Mvc.Routing;
using Kentico.PageBuilder.Web.Mvc.PageTemplates;
using Microsoft.AspNetCore.Mvc;

namespace Kickstart.Web.Features.LandingPages;

public class LandingPageController : Controller
{
    private readonly IContentQueryExecutor contentQueryExecutor;
    private readonly IWebPageDataContextRetriever webPageDataContextRetriever;
    private readonly IWebsiteChannelContext webSiteChannelContext;
    private readonly IPreferredLanguageRetriever preferredLanguageRetriever;

    public LandingPageController(
        IContentQueryExecutor contentQueryExecutor,
        IWebPageDataContextRetriever webPageDataContextRetriever,
        IWebsiteChannelContext webSiteChannelContext,
        IPreferredLanguageRetriever preferredLanguageRetriever)
    {
        this.contentQueryExecutor = contentQueryExecutor;
        this.webPageDataContextRetriever = webPageDataContextRetriever;
        this.webSiteChannelContext = webSiteChannelContext;
        this.preferredLanguageRetriever = preferredLanguageRetriever;
    }

    public async Task<IActionResult> Index()
    {
        var context = webPageDataContextRetriever.Retrieve();
        var builder = new ContentItemQueryBuilder()
                            .ForContentType(
                                LandingPage.CONTENT_TYPE_NAME,
                                config => config
                                    .Where(where => where.WhereEquals(nameof(WebPageFields.WebPageItemID), context.WebPage.WebPageItemID))
                                    .WithLinkedItems(1)
                                    .ForWebsite(webSiteChannelContext.WebsiteChannelName)
                                )
                            .InLanguage(preferredLanguageRetriever.Get());

        var queryExecutorOptions = new ContentQueryExecutionOptions
        {
            ForPreview = webSiteChannelContext.IsPreview
        };

        var pages = await contentQueryExecutor.GetMappedWebPageResult<LandingPage>(builder, queryExecutorOptions);

        var model = LandingPageViewModel.GetViewModel(pages.FirstOrDefault());
        return new TemplateResult(model);
    }
}

Content query API

Learn more about content query API and best practices for retrieving content items in Xperience by Kentico on our Retrieve content items documentation page.  

Register the controller

Now we need to tell Xperience to use this template whenever someone requests a page of the LandingPage type.

Use the RegisterWebPageRoute assembly attribute to create this association.

Thanks to this attribute, the content tree-based router (which you configured in the previous step) knows to use the controller.

You don’t need to set up any kind of routing table that references the controller.

C#
LandingPageController.cs


[assembly: RegisterWebPageRoute(
    contentTypeName: LandingPage.CONTENT_TYPE_NAME,
    controllerType: typeof(Kickstart.Web.Features.LandingPages.LandingPageController))]

The completed file should look like this code in the Kickstart repository.

Refactoring

The code samples here are meant to demonstrate important principles, but they are not production-ready.

In real-world scenarios, you will need to retrieve various content items frequently in your project. We recommend extracting content querying functionality into a separate service to make your code more modular and readable, as in in this example.

Depending on the size and lifetime of projects, you may also want to consider more complex enterprise architecture with a higher degree of abstraction.

Add the page template view

With the view model and controller in place, let’s move on to the view.

In the Features/LandingPages folder, create an empty Razor view called LandingPagePageTemplate.cshtml.

Designate the view’s model as TemplateViewModel. Then retrieve the LandingPageViewModel object from the controller by calling Model.GetTemplateModel.

Finally, display the model’s message and add a Page Builder editable area.

cshtml
LandingPagePageTemplate.cshtml


@using Kickstart.Web.Features.LandingPages

@model TemplateViewModel

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

<h3>@templateModel.Message</h3>

<editable-area area-identifier="areaMain" />

The TemplateViewModel lives in the Kentico.Content.Web.Mvc.PageBuilder namespace. Notice that we don’t need a using directive here because we added it to _ViewImports.cshtml in the previous step.

Register the page template

Now the landing page template exists in code, but Xperience cannot use it unless you register it.

In the current state, when execution hits your controller, Xperience will not know which view to use when the Index action returns TemplateResult.

To fix this, let’s create a new file to represent the template itself.

Set up the template file

Add a new file to the Kickstart.Web/Features/LandingPages folder named LandingPagePageTemplate.cs.

Define a class of the same name and add a string constant to represent a unique identifier for the template.

C#
LandingPagePageTemplate.cs

namespace Kickstart.Web.Features.LandingPages;
public static class LandingPagePageTemplate
{
    public const string IDENTIFIER = "Kickstart.LandingPagePageTemplate";
}

Add the registration attribute

Now we can use the IDENTIFIER constant to register the page template with the Xperience API.

Use the RegisterPageTemplate attribute, which takes the following parameters:

  • The identifier of the template, which Xperience will use when serializing template data in the database.
  • The display name for the template in the admin UI.
  • The path to the template’s view file.
  • A list of content types that the template supports.
  • An icon to represent the template in the admin UI.

If you don’t provide a list of allowed types, the template is allowed for all web page content types by default.

C#
LandingPagePageTemplate.cs


using Kentico.PageBuilder.Web.Mvc.PageTemplates;

using Kickstart;
using Kickstart.Web.Features.LandingPages;

[assembly: RegisterPageTemplate(
    identifier: LandingPagePageTemplate.IDENTIFIER,
    name: "Landing page content type template",
    customViewName: "~/Features/LandingPages/LandingPagePageTemplate.cshtml",
    ContentTypeNames = [LandingPage.CONTENT_TYPE_NAME],
    IconClass = "xp-market")]

namespace Kickstart.Web.Features.LandingPages;
public static class LandingPagePageTemplate
{
    public const string IDENTIFIER = "Kickstart.LandingPagePageTemplate";
}

Now the system knows that the view from the previous section is a page template. It will be available to any LandingPage pages that are allowed to use page templates.

Learn more about the RegisterPageTemplate attribute on this page of our documentation

In the next step, let’s check your progress by creating a homepage that uses this template.

Previous step: Configure the project to display content — Next step: Apply a page template

Completed steps: 7 of 13