DEV Community

Cover image for Kentico Xperience Design Patterns: MVC is Dead, Long Live PTVC
Sean G. Wright
Sean G. Wright

Posted on

Kentico Xperience Design Patterns: MVC is Dead, Long Live PTVC

I've written previously about the best ways to use Kentico Xperience's Page Builder. 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?

๐Ÿงฑ Keeping Content Separate From Design

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.

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.

๐Ÿ— Building Pages with MVC

If we look at the Kentico Xperience documentation on application 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 />:

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

<editable-area />
Enter fullscreen mode Exit fullscreen mode

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 ๐Ÿ˜€, 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.

๐Ÿ— Building Pages with Page Templates

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

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:

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

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:

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";
}
Enter fullscreen mode Exit fullscreen mode

Filter

We'll also have a Page Template Filter 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:

[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;
}
Enter fullscreen mode Exit fullscreen mode

You can find the code for PageTypePageTemplateFilter in the XperienceCommunity.PageTemplateUtilities 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 based project structure):

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

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

@model ComponentViewModel<HomePageTemplateProperties>

<!-- ... -->
Enter fullscreen mode Exit fullscreen mode

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)

๐ŸŽจ Configure Design in Page Template Properties

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:

@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>

<!-- ... -->
Enter fullscreen mode Exit fullscreen mode

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 with this feature ๐Ÿคฏ.

๐Ÿ–ผ Page Templates + View Components

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, 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:

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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):

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; }
}
Enter fullscreen mode Exit fullscreen mode

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

@model Sandbox.Features.Home.Components.HomePageViewModel

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

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

<!-- ... -->
Enter fullscreen mode Exit fullscreen mode

And, with that, we've cleaned up our Page Template, separated content and design, and covered all the parts of the PTVC pattern ๐Ÿฅณ๐ŸŽŠ!

๐Ÿ’ฐ Benefits of PTVC over MVC

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 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 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 allows to apply 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


Photo by Jordan Madrid on Unsplash

We've put together a list over on Kentico's GitHub account of developer resources. Go check it out!

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

#kentico

#xperience

Or my Kentico Xperience blog series, like:

Discussion (12)

Collapse
yuriyskentico profile image
Yuriy Sountsov

I may have missed it, is there a sample app that shows a full implementation of PTVC?

Collapse
seangwright profile image
Sean G. Wright Author

There's no link to external code, but there is also nothing special with the code. If you can create a Page Template and a View Component, then you've followed the pattern.

Was there something specific you were looking for an example of?

Collapse
dmueller78 profile image
Dave Mueller

Hi, Sean. Great article, thank you for taking the time to share it! Would you be willing to share a sample project (GitHub?) of the PTVC pattern being implemented in a generic Kentico 13 .Net Core Dancing Goat project in the interest of helping less experienced Kentico developers (like myself) to better understand the principle through a working example? I like the idea of PTVC, but just need a little more clarity on it that a sample project could provide. I've been evaluating Kentico 13 (.Net Core) for a couple months trying to pull as much from the official docs and the Dancing Goat sample project as possible. The problem I've run into now is that when I create a custom page template that needs business logic, I was guided into injecting a service class into the page template by Kentico docs. Now I'm trying to figure out how to shift to PTVC. Ultimately, I'm working toward custom business logic along with support for the page builder and have not been successful in achieving that. PTVC seems like it might be a great way to get there without resorting to custom routing and MVC controller-based logic. Thanks! -Dave

Thread Thread
seangwright profile image
Sean G. Wright Author

Dave,

Glad to hear you've been evaluating Kentico Xperience! It's a great platform!

The Dancing Goat project is really meant as a feature demo, and not necessarily an architectural/developer guidance project.

Check out this blog post to understand some of the nuance of the different project examples available out there:

devnet.kentico.com/articles/kentic...

If you are looking for a kitchen sink approach that will get you up and running fast (or you don't feel comfortable designing your own patterns and project internals), check out the Kentico Xperience 13 Baseline, maintained by my fellow MVP Trevor:

devnet.kentico.com/articles/get-st...

I don't have any good examples online at the moment of PTVC, but I could definitely provide an example (through a GitHub repo or Gist) that should point you in the right direction.

The key to PTVC is to take what you would have done in a Controller Action and move it to a View Component.

Then, in your Page Template, call that View Component to perform the business logic.

If the View Component needs context about the current Page to query for the right data from the database you can either pass that as a parameter to the View Component from the Page Template using pattern matching to get the correct type:

@* AboutUs Page Template *@
@if (Model.Page is not AboutUsPage aboutUs)
{
  return;
}

<vc:about-us-page page="aboutUs" />
Enter fullscreen mode Exit fullscreen mode

Or you can use the IPageDataContextRetriever in your View Component to get the current Page's information.

Does this help?

Thread Thread
dmueller78 profile image
Dave Mueller

That's very helpful, Sean. Thank you for the response with the links and clarifications. I'll give it a go! -Dave

Thread Thread
dmueller78 profile image
Dave Mueller

@seangwright I was able to get it working that way. The only thing I haven't been successful at is passing the page into the component as a parameter in the vc: tag... something I'm missing with the "pattern matching" I think. But I was able to use the alternative approach you suggested and get the page context from within the view component itself by way of the IPageDataContextRetriever. Thanks again for sharing your insight on the PTVC pattern and helping me along the way to implementing it. Cheers! -Dave

Thread Thread
seangwright profile image
Sean G. Wright Author • Edited on

Dave,

The key to the pattern matching is to 'exit early' if the type isn't correct. That was C# will know that in the rest of the View, the type is what you expect

@model ComponentViewModel<YourPageTemplatePropertiesType>

@if (Model.Page is not YourPageType myPage)
{
    return;
}

<!-- from here on, C# knows Model.Page is YourPageType and myPage is safely typecast as YourPageType -->
<vc:your-view-component page="myPage" props="Model.Properties" />
Enter fullscreen mode Exit fullscreen mode
public class YourModelTypeViewComponent : ViewComponent
{
     public IViewComponentResult Invoke(YourPageType page, YourPageTemplatePropertiesType props)
     {
          // ....
     }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
yuriyskentico profile image
Yuriy Sountsov

No, nothing specific, just the whole thing in general.

Collapse
chrisjwarnes profile image
Chris Warnes • Edited on

I think my issue with this approach is that it appears to work for simple cases, but not when things get more complex, one of the advantages of a controller is that you can create a view model with a base view model which has fields for use on the layout page. this can be important for adding information such as open graph tags or for when unique fall backs need to be managed for title/meta descriptions, or the header may need to be altered based on the current page for example.

As great as View Components are, they are not really a substitute for these kinds of scenarios.

Using Kentico in MVC5 currently has a different approach, I can still get all the advantages mentioned above (with the exception of all the dotnet core view component goodness) but I also have control of the output via a controller should I need this.

it seems a pity that kentico have followed this approach with dotnet core, whilst its possible you may have a simple enough project with no need for a controller, sometimes there are other complexities that need to be considered, the option to do either would be the perfect solution. Allowing developers to choose how they architecture a system is important, unless there are critical reasons why a particular approach will not work.

This is an excellent article about the advantages of page templates showing when to use fields on the template vs the page (which is super important) but it doesn't argue away the need for the MVC pattern. A combination of both would mean there is no need for any code smell anywhere. doesn't the fact that we have to write an article explaining how to avoid a code smell show there is a big potential problem here?

Keep up the good work Sean, I enjoy your articles.

Collapse
seangwright profile image
Sean G. Wright Author

Hey Chris, thanks for reading and thanks for the thoughtful reply!

I agree that for simply cases, Page Templates + View Components (or Child Actions in MVC5) is a great approach, and for advanced use cases you will want to use MVC Controllers.

you can create a view model with a base view model which has fields for use on the layout page

This can also be accomplished by setting values in a scoped service in the View Component used with a Page Template. The scoped service's value can then be read in the _Layout.cshtml because the Page's "View" is always rendered before the "Layout" is.

but I also have control of the output via a controller should I need this.

We can still use Controllers in ASP.NET Core for Page Templates if we want. In my examples I show how to route directly to the Template, but this is not required.

whilst its possible you may have a simple enough project with no need for a controller, sometimes there are other complexities that need to be considered, the option to do either would be the perfect solution.

Unless I misunderstand, the only thing we can't do with Page Templates, that you would like to see, is create a custom View Model class that is made available to the Page Template Razor View.

This is why I use View Components to do the heavy lifting of creating a custom View Model.

I see Page Templates as a 2 phase process:

  1. Xperience does all the processing to prepare the Page / Properties instances for the ComponentViewModel and route to / render the Page Template View.
  2. We then step in and use that 'context' that Xperience prepared for us, to fully customize what needs to be rendered (using View Components or other tools).

I'm glad that Xperience handles all of step 1 and step 2 still allows me to customize everything I need.

doesn't the fact that we have to write an article explaining how to avoid a code smell show there is a big potential problem here?

Maybe? The code smell isn't using MVC - it's putting design customization in Page Type fields (however a code smell isn't necessarily an indication of a bad design or a problem). I think we should try to avoid combining content with design, but sometimes it's going to be the best choice ๐Ÿคทโ€โ™‚๏ธ.

PTVC is a pattern that has helped me avoid that code smell where possible vs MVC, which only exists in ASP.NET because Ruby on Rails was popular back in the late 2000s. It's a pattern that the framework gives to Xperience to perform presentation logic and render HTML, but it's not the only one, and it wasn't designed to benefit content management in a CMS/DXP.

I recommend developers default to using PTVC, but I never stated that MVC is useless. I explicitly called out that it should be used if custom routing is required. I have not run into a situation using PTVC where I wasn't able to fully customize the rendered page, so I don't think that MVC brings me any additional 'power' here.

If you have use cases where MVC Controllers allow you to do things that PTVC doesn't, then that is a great reason to not use PTVC.

This post absolutely has a click-bait title ๐Ÿ˜, but it's also a 12 minute read (according to DEV), so hopefully I've added context to my claims.

In summary, I believe that with ASP.NET Core Kentico Xperience 13+ sites, developers should default to the PTVC pattern over MVC. If MVC brings something they need, then use that instead. All of this is only about presentation logic anyway - there's plenty of room left in an application for other design patterns that let developers build their custom solutions as needed.

Again, thanks for the thoughtful comment - I want the Xperience community to think about all these things, share ideas, and pick the solutions that work best for their projects. This discussion helps everyone!

Collapse
chrisjwarnes profile image
Chris Warnes

Hi Sean,

thanks for the quick reply, you say "We can still use Controllers in ASP.NET Core for Page Templates if we want." as far as I can see this is not the case for core, but you can do that if you build an MVC5 project.

if seems to be the difference to how the RegisterPageTemplate attribute is setup, in MVC5 you can point it to a controller which inherits from PageTemplateController, but this step doesn't appear to have been carried across to core, all you can do is specify a view.

MVC 5
[assembly: RegisterPageTemplate("CompanyName.HomeTemplate", typeof(HomeTemplateController), "Homepage template.")]

CORE
[assembly: RegisterPageTemplate("CompanyName.HomeTemplate", "Homepage template", "~/HomeTemplate/_HomeTemplate.cshtml")]

but i get your point about Scoped service, that could well be a way to approach the problem.

Thread Thread
seangwright profile image
Sean G. Wright Author

Ahhh, I understand what you're saying now ๐Ÿ˜.

We can create a Controller that handles rendering Pages of a specific type that also use Page Templates in ASP.NET Core and we can access the Page that is being handled by that Controller action method via the IPageDataContextRetriever service. We can do all kinds of custom logic here, populating scoped services, even preparing a View Model that we can retrieve in the Page Template View (while I don't recommend that approach, the DancingGoatCore app does this with the ArticlesController).

However this Controller is the one that returns the TemplateResult, which in MVC5 is 1 of 2 Controllers that can be defined.

You are correct ๐Ÿ‘๐Ÿ‘ - in ASP.NET Core we cannot create a custom Controller that prepares the View Model and selects the View to be rendered, like we can in MVC5.

Having worked a lot with Page Templates in MVC5, I always found this 'double Controller' pattern to be confusing and made for a lot of boilerplate, and I've found its removal in ASP.NET Core isn't a limitation - it just moves the process of preparing the View Model from a Controller into a View Component (or multiple View Components).

I'm definitely biased, having worked with ASP.NET Core since the pre-1.0 days, so I'm probably promoting the use of things like View Components and Tag Helpers (and dependency injection) more than developers that are new to ASP.NET Core are used to. I also think these tools can be leveraged to great effect in Kentico Xperience.

A lot of things we do in Kentico 12 MVC sites are not because they are great solutions, but because the Kentico team was working against the constraints of a framework initially designed in 2008-2009.

I feel like in ASP.NET Core, a lot of those constraints are gone and better patterns are available and should be explored or encouraged. Or, if they aren't available yet, the architecture is set up in a way to make them available in the future if the community is vocal about needing them (example: Form Builder Section Properties coming in KX 13 Refresh 2 at the end of the month).