How to Keep It Stupid Simple: Use .NET Core Razor Pages
Tags: csharp, headless, dotnet, webdev
Published: 2020-03-26 13:07:43

With recent improvements to the .NET platform and the ever-evolving CMS market, guidance on creating feature-rich websites is available at every turn. But what if you don’t have time for complicated tutorials and just need a modern, simple, easy-to-manage site?

With the release of .NET Core 3.x, advances in <a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/overview?view=aspnetcore-3.1" title="ASP.NET Core MVC" target="_blank">ASP.NET Core MVC</a> and the emergence of <a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/get-started?view=aspnetcore-3.1&tabs=visual-studio" title="Blazor preview" target="_blank">Blazor preview</a>, there is a lot of excitement in the .NET community. Although these improvements are wonderful, the robustness of MVC and the newness of Blazor may make some projects feel over-complicated. In addition to development complexity, accounting for CMS installations and upgrades can add extra overhead to a project. In the following article, I’ll discuss how <a href="https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-3.1&tabs=visual-studio" title="ASP.NET Core Razor Pages" target="_blank">ASP.NET Core Razor Pages</a> and <a href="https://kontent.ai/blog/the-importance-of-the-content-as-a-service-mindset" title="Content as a Service" target="_blank">Content as a Service (CaaS)</a> can help prevent overhead and complexity. If you’re a strong believer in the <a href="https://en.wikipedia.org/wiki/KISS_principle" title="KISS" target="_blank">KISS</a> principle like I am, I hope this article shows that your .NET development can be stupid simple!

<h2>Why Razor Pages?</h2>
<h3>Straightforward structure</h3>
ASP.NET Core Razor Pages takes a page-focused approach to the project structure. It co-locates a page’s view and its PageModel (logic pertaining to a view) in a “Pages” directory. If you’ve modeled your content and identified that the majority of it is structured around the concept of a “page,” then Razor Pages may be the perfect framework for your project.

For example, in my sample coffee shop project, I have a lot of basic informational pages about coffee and cafes. If I wanted to make changes to the “Cafes” page, I’d navigate to <i>~/Pages/Cafes/Index.cshtml</i> to edit the view or <i>~/Pages/Cafes/Index.cshtml/Index.cshtml.cs</i> to edit the logic. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vnvoarfy7hn964fenwuj.png)

This single-responsibility-style structure makes finding the code for specific pages less difficult since pages are found in the “Pages” folder.

<h3>Familiarity</h3>
If you’ve ever used Web Forms, the above description of layout and logic being colocated should sound very familiar.  Razor Pages is sometimes viewed as the successor to Web Forms due to its structure, but it comes with <a href="https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-3.1#why-choose-aspnet-core" title="features of .NET Core" target="_blank">modern features of .NET Core</a>.  So if you’ve been dreading the transition from Web Forms to MVC in order to use .NET Core features, I’m sure Razor Pages will be a welcoming experience.

<h3>Options</h3>
Routing can be very confusing or prone to errors. This being said, instead of using MVC’s less straightforward controller -> action -> view approach, default routing in a Razor Pages project uses the location of a page within the project to determine its URL.  
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ic6xm2butqifj47n2dve.png)

For example, a request to “http://<i>domain</i>/Articles” will be handled by <i>~/Pages/Articles/Index.cshtml</i> and its respective .cs file, while a request to “http://<i>domain</i>/articles/detail” will be handled by <i>~/Pages/Articles/Detail.cshtml</i> and its .cs file.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0tq38o6m5y90xsiog38y.png)

Although this behavior helps simplify routing, it doesn’t prevent you from having custom routes.  For example, in my sample project, I opted to use an <a href="https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-3.1&tabs=visual-studio#custom-routes" title="page directive" target="_blank">@page directive</a> to append a segment to my article URLs in order to return a specific article and present a practical address to visitors. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/tsk2k8pyca2wclvo6nja.png)

In the Articles/Detail.cshtml view, the directive <i>@page “{UrlPattern}”</i> is used. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1ga4dzl9eezyb5zdjwng.png)

This <i>UrlPattern</i> value is used in the page’s logic to return content from my headless CMS’s API. We’ll discuss my choice of Content-as-a-Service/headless CMS choice later in this article, but for now, rest assured that the <i>@page</i> directive allows me to add a segment to a URL such as “http://<i>domain</i>/Articles/Detail/<b>coffee-processing-techniques</b>.”

In addition to page directives, Razor Pages projects can use <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.mvcservicecollectionextensions.addrazorpages?view=aspnetcore-3.1" title="AddRazorPages" target="_blank">AddRazorPages</a> and <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.mvcrazorpagesmvcbuilderextensions.addrazorpagesoptions?view=aspnetcore-3.1" title="AddRazorPagesOptions" target="_blank">AddRazorPagesOptions</a> on the service collection in the Startup class. This opens up a lot of routing possibilities described in the official <a href="https://docs.microsoft.com/en-us/aspnet/core/razor-pages/razor-pages-conventions?view=aspnetcore-3.1" title="Microsoft documentation for Razor Pages routing" target="_blank">Microsoft documentation for Razor Pages routing</a>. To showcase this, I used custom routing for my homepage. I wanted to organize the homepage in a sub-folder similar to the other pages in my site, and I didn’t want “/Home” in the URL. This isn’t a complicated routing scenario, but you can see how this approach allows for more flexible routing.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lgjknwqwvt7tlsr53yr3.png)

Although I’ve focused on the page-centric structure thus far, that doesn’t mean that your project is limited to pages. Functionality that is distinct from pages or that can be reused across multiple pages can be implemented using <a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-3.1" title="ViewComponents" target="_blank">ViewComponents</a>. For example, in my project, I created a Components folder within Pages that contains the logic and view for displaying contact information. To use this component in my site’s footer, I just call ```C# @await Component.InvokeAsync("Contact")``` in my shared layout. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/d4yq3krjsmozl3u120oh.png)

Although this adds a folder within Pages that isn’t directly related to any given page, this component is responsible for rendering content to pages, so it still seems sensible to have it in this location.

These are a few examples of how Razor Pages can allow developers to reap the numerous benefits of modern features provided in .NET Core while also keeping their project as simple as possible. This being said, writing code is only a fraction of the effort it takes to create a great website. How can the theme of “simplicity” extend to the content management of the project? 

<h2>Why CaaS?</h2>
For starters, what <i>is</i> “CaaS?” <i>Content as a Service</i> is an approach to creating content in a Software as a Service content management system and delivering that content over multiple channels using an API. You might be thinking, “That sounds a lot like a headless CMS...” Exactly! The CaaS approach to content creation leverages a headless CMS but also includes various services, bundles, or offerings in addition to giving editors a CMS and developers an API. For the sake of this article, you can substitute “CaaS" for “headless CMS” if it better fits your experience.

<h3>No CMS maintenance</h3> 
If you’ve ever managed an on-premise CMS or have been involved in CMS deployment and maintenance, you know that it comes with a fair amount of overhead. Installations, managing maintenance windows, doing upgrades, applying bug fixes, upgrading infrastructure... The list goes on and on. These tasks take time away from development and content creation. With a Content-as-a-Service offering, the vendor takes care of the CMS and infrastructural maintenance, which allows you to focus on development.

<h3>Omnichannel</h3> 
Even though we’re trying to keep our projects simple, the world is not a simple place. Customers have complex needs. In 2020 and beyond, customers are going to expect their content to be available on websites, mobile applications, voice assistants, and other channels. By creating your content in a system or service that is platform agnostic and can be delivered to any channel over an API, you’re ensuring the future expansion of your project to these other mediums is possible.

<h3>Structured content</h3>
In addition to the omnichannel benefits, working with CaaS makes development easier by ensuring content is structured. It’s a common practice for CaaS products to send API responses in either JSON or XML format. This streamlines the development process by making content returned from the API predictable and consistent across the project. If you’ve done proper content modeling in the early stages of development, there shouldn’t be any guessing involved when using a response from your CaaS API. Minimizing guesswork with uniform, structured content delivery will definitely make the development process smoother.

<h2>Why Kentico Kontent?</h2>
For my sample project, I chose to use <a href="https://kontent.ai" title="Kentico Kontent" target="_blank">Kentico Kontent</a> as my CaaS offering. In addition to meeting all of the requirements listed in the Why CaaS? section above, <a href="https://github.com/Kentico/kontent-delivery-sdk-net" title="Kontent .NET SDK" target="_blank">Kentico Kontent’s open-source .NET SDK</a> and supporting tools further improve the development experience.

<h3>.NET SDK</h3> 
In 2020, having a clean API is not the pinnacle of a pleasant developer experience. I’d argue that many developers leveraging an API service jump into SDK documentation almost as quickly as the base API documentation itself. With this in mind, Kentico Kontent’s .NET SDK has sensible naming conventions, <a href="https://github.com/Kentico/kontent-delivery-sdk-net#kentico-kontent-delivery-net-sdk" title="Kontent .NET SDK documentation" target="_blank">extensive documentation</a>, and <a href="https://github.com/Kentico/kontent-delivery-sdk-net/wiki/Instantiating-DeliveryClient-with-Configuration-API-and-DI-in-ASP.NET-Core-MVC-apps" title="Kontent .NET SDK documentation" target="_blank">dependency injection support</a> that makes setting up the SDK and querying content a breeze. For example, other than the code related to dependency injection and strong typing, my call for retrieving an “About Us” item from Kentico Kontent is a single line of code:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ik9wt7dwr60fp5q371ge.png)

This code returns a structured JSON response containing all the details about my “About Us” content item that I created in the Kentico Kontent user interface, then casts it to my AboutUs model.

Setting up the application to use dependency injection with the SDK can be accomplished in a single service registration in the Services.cs file that uses a standard configuration in an appsettings.json file:
<h4>Startup.cs</h4>
```C#
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
 
    public IConfigurationRoot Configuration { get; }
 
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDeliveryClient(Configuration);
    }
}
```
<h4>appsetings.json</h4>
```JSON
{
    "DeliveryOptions": {
        "ProjectId": "<Kontent_ProjectId_Value>"
    }
}
```

Although I’ve focused on a minimal setup in the above examples, don’t worry! If your project needs more advanced functionality, the Kentico Kontent .NET SDK has lots of features and options, making it scale comfortably with increasing demands or complexity.

<h3>Model generator</h3> 
When using a Model-View-ViewModel design pattern like Razor Pages, creating and updating models can be a considerable amount of upfront and ongoing work, especially if your content creators or project managers change the content structure or requirements. To account for this, Kentico Kontent has an open-source tool that automatically generates strongly-typed models for your project. If you rely on strongly-typed models for IntelliSense, hate manually editing models, and don’t mind using a CLI tool to take on this otherwise tedious task, you’re going to love that Kentico Kontent provided the <a href="https://github.com/Kentico/kontent-generators-net#kentico-kontent-model-generator-utility-for-net" title="Kontent model generator for .NET" target="_blank">Kontent model generator for .NET projects</a>.

<h3>Not just for developers</h3>
In addition to the development perks, a headless CMS will benefit editors and their managers too. If you’re interested in content creation features, check out <a href="https://kontent.ai/marketers" title="Kontent for marketers" target="_blank">this page</a>, or you can <a href="https://app.kontent.ai/sign-up" title="try Kontent out" target="_blank">try it out</a> for yourself. No manuals required!

<h2>Summary</h2>
In this article, we discussed what aspects of ASP.NET Core Razor Pages make it a great framework for creating simple, but modern websites in both structure and feature sets. We also described how pairing Razor Pages with a CaaS offering like Kentico Kontent can further reduce development overhead. I hope that demonstrating my <a href="https://github.com/Kentico/kontent-sample-app-razorpages" title="sample project" target="_blank">sample project</a> showed you how we can keep it simple in 2020 and beyond!