Copyright Johan Kronberg 2009-2024 Latest posts RSS feed X: johankronberg LinkedIn Profile GitHub: krompaco
Site generated by: Record Collector

A project structure for Razor Pages and View Components with CMS 12

Some might have missed that Razor Pages can be used with an Optimizely CMS app so I thought I'd explore some options and find a streamlined setup that works with Visual Studio tooling.

Outside of CMS sites I have worked quite a bit with Razor Pages and like it more than MVC. Some benefits I've seen are:

  • More convention based.
  • Simpler to work with forms.
  • Tooling don't default to end up with 999 files named Index.cshtml.

To me, it's faster to create new pages and to look them up for doing maintenance than with MVC.

Page templates

The documentation for using Razor Pages as content templates shows some things but I don't think there is a full sample site out somewhere.

Anyway, just having your Razor Page inherit RazorPageModel<T> from EPiServer.Web.Mvc is the basis, so something like public class StartPageModel : RazorPageModel<StartPage> will get your StartPage PageData object set as CurrentContent and available to use in the .cshtml part of the Razor Page.

Partial views

For blocks I've been a bit burnt by the flexibility that is, and has been, available.

Having block rendering both with and without Controller and various hack-ish ways to get them rendered for e-mails, API responses and in- and outside of ContentArea-instances has been a big time consumer when upgrading from 11 to 12 and hard to explain.

Therefore, I think for both MVC and Razor Pages and new things, I want to favor having as many partial things as possible built as a full View Component.

This, I hope, will make it easier to handle rendering anywhere in a cohesive way and easier to explain in general. At minimal, a View Component for a Block looks like this:

namespace WebApp.Pages.Components.TeaserBlock;
public class TeaserBlockViewComponent : AsyncPartialContentComponent<ContentTypes.Blocks.TeaserBlock>
{
    protected override async Task<IViewComponentResult> InvokeComponentAsync(ContentTypes.Blocks.TeaserBlock currentContent)
    {
        return this.View(nameof(TeaserBlock), currentContent);
    }
}

Notice the nameof() part, this makes the View file not have to be named Default.cshtml and therefore easier to find.

I can still use the warning in Visual Studio with ReSharper and have the "missing" View be created, with the good name I want, and in the right folder.

There is also nothing here that requires any custom view locator code or added configuration parts.

Structure inside a project

So, at the root of a sample web project this is what it looks like.

  • Pages
    • Components
      • TeaserBlock
        • TeaserBlock.cshtml
        • TeaserBlock­ViewComponent.cs
    • Shared
      • _Layout.cshtml
    • StartPage.cshtml
      • StartPage.cshtml.cs

Screenshot of the listed folder structure

I've only done a short experiment but haven't found anything against this direction this far.

It also ties in nicely with some ViewModelBuilder pattern that you might want to use.

I don't see anything that must be done with Content Type models in this setup so keep doing whatever works for you with those. I think it would be easy to have a feature folders setup for this approach if you favor that.

Comments?

Published and tagged with these categories: Optimizely, CMS, ASP.NET