Practical .NET

Building Razor Pages

If you want to handle the most common pattern in ASP.NET Controllers (displaying a page and then accepting data entered into it), you can do it with Razor Pages. You'll just need less code than if you used a Controller, a View and a model object.

As I noted in an earlier column, I like Razor Pages very much -- not something I originally expected when I first read about them. In this column, I'll walk you through creating a typical Razor Page in the hopes of convincing you to agree with me.

What Has and Hasn't Changed
A Razor Page typically has two components: a View file (the .cshtml file) and a code file (the .cshtml.cs file). You can (I'm told) skip the code file by putting all of the code for your page in the View enclosed in a function block, like this:

@functions
{
 // ... page code ... 
}

However, this immediately makes it difficult (I want to say "impossible") to do any automated testing with your code. And, it violates the Single Responsibility Principle because you now have two reasons for changing the contents of the .cshtml file: if your UI changes in either layout/styling or if your business logic changes.

Working with the View
The good news here is that the View component of the Razor Page (the .cshtml file) works exactly the same way as the Views you're used to from working with Controllers. The major difference is external: The URLs you use to access a page aren't (by default) divorced from the page's location in your project the way that Controllers and Action methods are.

By default all your Razor Pages must reside in your project's Pages folder. Also by default, you access Pages with URLs that use the Pages folder as if it were the root of your Web site (you can override these defaults but I'm not clear why you'd want to). So, if you have a page called CustomerManagement in your Pages folder, the URL for retrieving that Page is http://<server name>/CustomerManagement. A Page called CustomerManagement.cshtml in the Pages/Customers folder would be accessed through http://<server name>/Customers/CustomerManagement, and so on. You can customize the default routing rules but I'll leave that for a later column.

The major change in your View is the class name you use in your model directive at the top of your View: The standard Razor Page is to have the model directive point to the class file in the Page's .cshtml.cs file. That class typically has the same name as your Razor Page but with the word "Model" tacked on at the end. So, for a Page called CustomerManagement, the cshtml file would begin:

@model CustomerManagementModel

And the code file would begin:

public class CustomerManagementModel: PageModel

Other than that, though, not much changes. The public properties in the class file in your *Model class (the cshtml.cs) file now act like the properties in the "model" class you used to pass to your View. All you have to do is load those properties with some data in *Model class's code.

Razor Page Methods
In an ASP.NET MVC Controller, many pages have two methods with matching names but different parameter lists. Typically one method accepts a small number of values (usually passed in the URL) and uses those parameter values to create a View model object you then passed to a View. This method was used to satisfy GET requests; the second method typically accepts the view model object and was used to satisfy POST requests. Typical code looks like this:

public class CustomerManagement
{
  public ActionResult GetCustomer(int Id)
  {
    Customer cust = CustomerRepository.GetCustomerById(Id);
    return View(cust);
  }
  [HttpPost]
  public ActionResult GetCustomer(Customer cust)
  {
    CustomerRepository.Update(cust);
    return RedirectToAction("GetCustomer", new {Id = cust.Id});
  }

In a Razor Page, the code is simpler. First, the method to handle GET requests is called OnGet and the method to handle POST requests is called OnPost (no attributes required). There's no need to return anything from either method: Both methods just update the properties in your *Model class. Typical *Model code in the .cshtml.cs file looks like this:

public class CustomerManagementModel : PageModel
{
  public Customer viewCust { get; set; }

  public void OnGet(int Id)
  {
    viewCust = CustomerRepository.GetCustomerById(Id);  
  }
  public void OnPost (Customer cust)
  {
    CustomerRepository.Update(cust);
  }

As written, this code won't quite work because I haven't specified where the OnGet method will get the Id parameter. I can do that with attribute-based routing (though I present what I think is a better alternative later on).

I also don't need the OnPost method to accept a parameter. If I add the BindProperty attribute to my viewCust property, then any data posted back from the browser will be put in my viewCust property. With both of those changes, my Page's code file will look like this:

public class CustomerManagementModel : PageModel
{
  [BindProperty]
  public Customer viewCust { get; set; }

  [Route("Id: int")]
  public void OnGet(int Id)
  {
     viewCust = CustomerRepository.GetCustomerById(Id);
  }
  public void OnPost ()
  {
     CustomerRepository.Update(viewCust);
  }

If I want, I can set the BindProperty's SupportsGet property to true, and then I don't have to accept parameters in my OnGet method, either ... which also means that I don't need a Route attribute on my OnGet method. That code would look like this:

  [BindProperty(SupportsGet = true)]
  public Customer viewCust { get; set; }

  public void OnGet()
  {
     viewCust = CustomerRepository.GetCustomerById(viewCust.Id);
  }       

This does drive a change in the Razor code in the View component of my Razor Page (the .cshtml file): I'll have to bind my HTML to the viewCust property I've set up in my code file. Typical Razor code would look like this:

@page
@using NETCoreSDK20.Pages
@model CustomerManagementModel

@using (Html.BeginForm())
{
    @Html.EditorFor(m => m.viewCust.Id) 
@Html.EditorFor(m => m.viewCust.FirstName)

As I said in that earlier column, when I first read about Razor Pages I thought of them as a kind of toy for creating simple-minded Web pages. The more that I've come to work with them, the more I've come to think that they're the superior model for building Web pages. You're free to disagree, of course.

There are at least two more things to be said here, though. First, it's not unusual to have a View that does more than just Gets and Posts (adding and deleting customers, for example), and second, I think you shouldn't tightly couple your Page's URLs to their current names and locations. I'll cycle back to those topics in the near future.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

Terms and Privacy Policy consent

I agree to this site's Privacy Policy.