Razor Pages

All Razor files end with .cshtml. Most Razor files are intended to be browsable and contain a mixture of client-side and server-side code, which, when processed, results in HTML being sent to the browser. These pages are usually referred to as "content pages". This section takes a deeper look at content pages, and their associated PageModel files.

Content Pages

For a file to act as a Razor content page, it must have three characteristics:

  • It cannot have a leading underscore in its file name
  • The file extension is .cshtml
  • The first line in the file is @page

Placing the @page directive as the first line of code is critical. If this is not done, the file will not be seen as a Razor page, and will not be found if you try to browse to it. There can be empty space before the @page directive, but there cannot be any other characters, even an empty code block. The only other content permitted on the same line as the @page directive is a route template.

Content pages can have a layout file specified, but this is not mandatory. They can optionally include code blocks, HTML, JavaScript and inline Razor code.

Razor Syntax

Content pages are largely comprised of HTML, but they also include Razor syntax which enables the inclusion of executable C# code within the content. The C# code is executed on the server, and typically results in dynamic content being included within the response sent to the browser.

Single File Approach

Although not recommended, it is possible to develop Razor Page applications that rely solely on content pages. The following example features an approach that is most like that which is familiar to developers with a scripting background, like PHP or classic ASP:

Example.cshtml:

@page 
@{
    var name = string.Empty;
    if (Request.HasFormContentType)
    {
        name = Request.Form["name"];
    }
}

<div style="margin-top:30px;">
    <form method="post">
        <div>Name: <input name="name" /></div>
        <div><input type="submit" /></div>
    </form>
</div>
<div>
    @if (!string.IsNullOrEmpty(name))
    {
        <p>Hello @name!</p>
    }
</div>

The Razor content page requires the @page directive at the top of the file. The HasFormContentType property is used to determine whether a form has been posted and the Request.Form collection is referenced within a Razor code block with the relevant value within it assigned to the name variable.

The Razor code block is denoted by an opening @{ and is terminated with a closing }. The content within the block is standard C# code.

Single control structures do not need a code block. You can simply prefix them with the @ sign. This is illustrated by the if block in the preceding example.

To render the value of a C# variable or expression, you prefix it with the @ sign as shown with the name variable within the if block.

Functions Blocks

The next example results in the same functionality as the previous example, but it uses a @functions block to declare a public property which is decorated with the BindProperty attribute, ensuring that the property takes part in model binding, removing the need to manually assign form values to variables.

Example.cshtml:

@page

@functions {
    [BindProperty]
    public string Name { get; set; }
}

<div style="margin-top:30px;">
    <form method="post">
        <div>Name: <input name="name" /></div>
        <div><input type="submit" /></div>
    </form>
    @if (!string.IsNullOrEmpty(Name))
    {
        <p>Hello @Name!</p>
    }
</div>

This approach is an improvement on the previous in that is makes use of strong typing, and while the processing logic can be restricted to the @functions block, the page will become more difficult to maintain and test. Nevertheless, it is possible to declare methods and even nested classes in the @functions block.

You can also use the @functions block to declare local methods that include HTML to act as display helpers for the current page. You might do this if a page has multiple blocks of code that included HTML and require similar formatting to be applied. This is only possible in ASP.NET Core 3.x

In the following examples, a method is declared that formats DateTime values:

@functions{
    void DisplayDate(DateTime dt)
    {
        <span class="date">@dt.ToString("dddd, dd MMMM yyyy")</span>
    }
}

Wherever you want to render a date with this formatting in a page, you would do the following:

@DisplayDate(myDate)

This is useful if you ever need to modify the output, since you only need to make changes in one place.

It is possible to do something similar in Razor Pages 2.x, but the method must be declared in a code block as follows:

@{
    Func<DateTime, IHtmlContent> DisplayDate = @<span class="date">@item.ToString("dddd, dd MMMM yyyy")</span>;
}

The item variable is a special one that refers to the first parameter passed in to the method. Usage is the same, but you will need to add a using directive for Microsoft.AspNetCore.Html.

PageModel Files

The recommended way to develop Razor Pages applications is to minimise the amount of server-side code in the content page to the barest minimum. Any code relating to the processing of user input or data should be placed in PageModel files, which share a one-to-one mapping with their associated content page. They even share the same file name, albeit with an additional .cs on the end to denote the fact that they are actually C# class files.

The following code shows the Example.cshtml file adapted to work with a PageModel:

Example.cshtml:

@page
@model ExampleModel

<div style="margin-top:30px;">
    <form method="post">
        <div>Name: <input asp-for="Name" /></div>
        <div><input type="submit" /></div>
    </form>
    @if (!string.IsNullOrEmpty(Model.Name))
    {
        <p>Hello @Model.Name!</p>
    }
</div>

And this is the code for the PageModel class:

Example.cshtml.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPages.Pages
{
    public class ExampleModel : PageModel
    {
        [BindProperty]
        public string Name { get; set; }
    }
}

The PageModel class has a single property defined as in the previous example, and it is decorated with the BindProperty attribute. The content page no longer has the @functions block, but it now includes an @model directive, specifying that the ExampleModel is the model for the page. This also enables Tag helpers in the page, further taking advantage of compile-time type checking.

The default projects generate content pages paired with PageModel files. This is the recommended approach. However, it is also useful to know how to work with content pages without a PageModel for circumstances where they are not needed.

Last updated: 27/03/2021 22:44:29

© 2018 - 2024 - Mike Brind.
All rights reserved.
Contact me at Outlook.com