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.
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.
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:
IWebPageDataContextRetriever
to figure out which Landing page led to the controller.ContentItemQueryBuilder
to define the parameters of the query.IWebSiteChannelContext
to decide which website channel’s pages to pull from and determine whether unpublished content should be retrieved for preview mode.IPreferredLanguageRetriever
to determine which language version of the content you need.IContentQueryExecutor
to retrieve the data and map it to aLandingPage
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
.
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.
[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.
@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.
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.
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