Skip to content
Xperience by Kentico StyleguideXperience by Kentico StyleguideXperience by Kentico Styleguide
GitHubYouTube

Page Builder

Routing

Default to using Page Templates (+ View Components)

Recommended

Why?

Page Templates provide a great place to manage page-level design customization with Page Template properties - something Controller routing and Route-to-View routing do not have.

Why?

Page Templates enabling toggling between several designs for a given page, or growing the number of layouts as a solution matures. This is also possible with Controller routing, but developers will need to re-create the functionality.

Razor

Use Tag Helpers intead of HTML Helpers

Recommended

Why?

Xperience by Kentico has kept HtmlHelper methods and extensions to make the upgrade from Kentico 12 MVC and Kentico Xperience 13 easier for teams. However, they are part of ASP.NET Core’s legacy from MVC 5. Tag Helpers were designed to replace Html Helpers.

Why?

Front-end developers who are more familiar with HTML than Razor and C# will have a harder time using and reading HTML Helpers than Tag Helpers because Tag Helpers look like and often behave like native HTML. By using Tag Helpers, we can make our projects more accessible and welcoming to developers new to ASP.NET Core.

Why?

Tag Helpers are much more common in ASP.NET Core projects than HTML Helpers and the built-in ASP.NET Core Tag Helpers are frequently used. We should align with the conventions of modern ASP.NET Core where Xperience enables us.

Components

Avoid the Built-In Default Section

Suggested

Why?

The Page Builder is where structured content is composed with presentational experiences and design. A marketer’s Page Builder experience should be tailored to their site’s content and design requirements.

Why?

Xperience’s included Default Section is great for enabling developers to get started quickly but it wasn’t designed for our site’s needs.

Why?

We should be using the allowed-widgets and allowed-sections restrictions of each <editable-area /> anyway.

Co-locate Page Builder registration, types, and values

Recommended

  • Define Page Template, Widget, and Section Identifiers close to their registration attributes
  • Define component Properties and View Model classes close to the components that use them
  • Define component registration attributes near where they are defined

Why?

Things that change together should stay together. A component should only be removed if its identifier is also removed. Keeping these two things close together helps ensure this consistency.

Why?

Navigation to the definition of a component identifier - if it’s referenced through a variable - should bring a developer close to the definition of the associated component.

Why?

As Joel Spolsky stated, “the more information about what code is doing is located right in front of your eyes, the better a job you’ll do at finding the mistakes”.

public static class ComponentIdentifiers
{
    // Sections
    public const string SINGLE_COLUMN_SECTION = "KBank.SingleColumnSection";
    public const string FORM_COLUMN_SECTION = "KBank.FormColumnSection";

}
// ~/Components/Widgets/Heading/HeadingWidget.cs
public class HeadingWidget : ViewComponent
{
    // ...
}

// ~/Components/Widgets/Heading/HeadingWidgetProperties.cs
public class HeadingWidgetProperties : IWidgetProperties
{
    //...
}

// ~/Components/Widgets/Heading/HeadingWidgetViewModel.cs
public class HeadingWidgetViewModel
{
    //...
}
// ~/Components/Widgets/Heading/HeadingWideget.cs
using Sandbox.Components.Sections.Heading;
using Kentico.PageBuilder.Web.Mvc;
using Microsoft.AspNetCore.Mvc;

[assembly: RegisterSection(
    identifier: HeadingSection.IDENTIFIER,
    viewComponentType: typeof(HeadingSection),
    name: "Heading",
    propertiesType: typeof(HeadingSectionProperties),
    Description = "Heading Section to be used at the top of a page",
    IconClass = "icon-i"
)]

namespace Sandbox.Components.Sections.Heading;

public class HeadingSection : ViewComponent
{
    public const string IDENTIFIER = "Sandbox.Sections.Heading";

    public IViewComponentResult Invoke(ComponentViewModel<HeadingSectionProperties> vm)
    {
        var model = new HeadingSectionViewModel { };

        View("~/Components/Sections/Heading/HeadingSection.cshtml", model);
    }
}

public class HeadingSectionProperties : ISectionProperties { }

public class HeadingSectionViewModel { }

Access Component Identifiers through an Abstraction

Suggested

  • Create a static abstraction to access component identifiers if their use is statically defined (typical)
  • Create an injectable service abstraction to access component identifiers if their use is dynamically defined (advnaced)

Why?

As mentioned above, component identifiers should be defined near the component definition, but accessing them for <editable-area> and <widget-zone> Tag Helpers should be convenient.

Why?

An abstraction can give developers easier discoverability of the components available in a solution.

Why?

If needed, the abstraction can be registered as a scoped service in the ASP.NET Core DI container and injected into the view.

// ~/Components/Widgets/WidgetIdentifiers.cs
namespace Sandbox.Components.Widgets;

public static class WidgetIdentifiers
{
    public static class Accordions
    {
        public const string Accordion = AccordionWidget.IDENTIFER;
    }

    public static class Badges
    {
        public const string BadgesList = BadgesListWidget.IDENTIFIER;
    }

    public static class CTAs
    {
        public const string CTALink = CTALinkWidget.IDENTIFIER;
        public const string CTAClinicalTrialLink = CTAClinicalTrialLinkWidget.IDENTIFIER;
    }

    public static class Forms
    {
        public const string Form = SystemComponentIdentifiers.FORM_WIDGET_IDENTIFIER;
        public const string CTAForm = CTAFormWidget.IDENTIFIER;
        public const string InlineForm = InlineFormWidget.IDENTIFIER;
    }

    // ...
}