Troubleshooting Razor Pages and PageModel Classes

Date Published: 27 August 2017

Troubleshooting Razor Pages and PageModel Classes

When using the new Razor Pages feature in ASP.NET Core 2.0, you can (and usually should) define a separate “codebehind” file for your PageModel class. If you’re using Visual Studio, this class will be associated with your Razor Page as a nested file in the Solution Explorer, making it easy to locate. The class will also be easier to unit test than if you were to put your functionality directly into your Razor Page file, no different than code you might have chosen to put into a Razor View file. But what happens if you combine functionality between the two files? Consider these two files:

// NestedModels.cshtml
@page
@model NestedModels
@using Microsoft.AspNetCore.Mvc.RazorPages
@{
    ViewData["Title"] = "About";
}
 
@functions{
    public class NestedModels : PageModel
    {
        public string Message { get; set; }
 
        public void OnGet()
        {
            Message = "OnPage.OnGet: Your application description page.";
        }
 
        public void OnPost()
        {
            Message = $"OnPage.OnPost: Server time: {DateTime.Now}.";
        }
    }
}
 
<h2>@ViewData["Title"]</h2>
<h3>@Model.Message</h3>
 
<p>Use this area to provide additional information.</p>
 
<form method="post">
    <input type="submit" value="Submit" />
</form>
// NestedModels.cshtml.cs
using System;
using Microsoft.AspNetCore.Mvc.RazorPages;
 
namespace WebApplication12.Pages
{
    public class NestedModels : PageModel
    {
        public string Message { get; set; }
 
        public void OnGet()
        {
            Message = "NestedModels.Codebehind OnGet";
        }
 
        public void OnPost()
        {
            Message = $"NestedModels.Codebehind OnPost. ServerTime: {DateTime.Now}";
        }
    }
}

In the above example, there are duplicate OnGet and OnPost methods in the Razor Page and its PageModel. What happens? In this case, the page version “wins” and is the one that is called. Both the initial page load (GET) and submit (POST) show the “OnPage” messages. Looking closely at this code, you can see that there are actually two PageModels classes being defined. Why doesn’t this generated a compilation error due to the duplicate class name?

The answer comes from understanding how the Razor engine parses the Razor Page and generates the resulting C# code. Thanks to Dan Roth for his explanation here, as well as the second scenario below. The code generated by the Razor page ends up looking something like the following GeneratedPage class:

public class GeneratedPage
{
    public NestedModels Model { get; set; }
 
   public class NestedModels Model
    {
    }
}
 
public class NestedModels
{
}

The model defined on the page itself becomes a nested class within the generated page class. The codebehind page model is a separate class. C#, when faced with this scenario, will use the nested class type before another class with the same name, and thus we get the behavior shown.

What about mixing properties in the pagemodel with handlers on the page itself, like this?

// About.cshtml
@page
@model AboutModel
@using Microsoft.AspNetCore.Mvc.RazorPages
@{
    ViewData["Title"] = "About";
}
 
@functions{
    public void OnPost()
    {
        Message = "Some message.";
    }
}
 
<h2>@ViewData["Title"]</h2>
<h3>@Model.Message</h3>
 
<p>Use this area to provide additional information.</p>
 
<form method="post">
    <input type="submit" value="Submit" />
</form>
// About.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
 
namespace WebApplication12.Pages
{
    public class AboutModel : PageModel
    {
        public string Message { get; set; }
 
        public void OnGet()
        {
            Message = "Your application description page.";
        }
    }
}

In this example, the page will have a compile error:
The name ‘Message’ does not exist in the current context
+
Message = “Some message.”;

In this example, the generated code looks something like this:

public class GeneratedPage
{
    public AboutModel Model { get; set; }
 
    // Not used as a page handler because you have an explicit page model!
   public void OnPost()
    {
        Message = "Your application description page.";
    }
}
 
public class AboutModel : PageModel
{
    public string Message { get; set; }
 
    public void OnGet()
    {
        Message = "Your application description page.";
    }
}

The OnPost method is part of the page, but the Message property is on the separate AboutModel class. Thus, the page can’t reference the Message property, and a compilation error results. If you don’t specify a page model, the page itself is used as the page model, but in this case the page did specify the separate AboutModel class as its page model, and so it is used. Access to the model is available from the page’s Model property, however, so this code would work:

@functions{
    public void OnPost()
    {
        Model.Message = "Some message.";
    }
}

Recommendations

If your Razor Page has any real logic on it, and is doing more than just displaying values, I recommend you use a separate PageModel in a separate class file. When doing so, place all of the functionality in this class, and avoid having any methods (or classes!) defined in your @functions block in your page. As with MVC, I strongly prefer to have “dumb” views since views are more difficult to test than controllers, which are more difficult to test than other classes, typically. The same holds true with Razor Pages. Keep logic out of your Razor Page, and minimize how much of it is in your PageModel. Prefer placing important business logic into separate services or inside your domain model, so that your UI layer is thin and simple.

Steve Smith

About Ardalis

Software Engineer

Steve is an experienced software architect and trainer, focusing currently on ASP.NET Core and Domain-Driven Design.


Ardalis

Copyright © 2020