Razor Pages - Getting Started With The Preview

Current users of ASP.NET Web Pages have been eagerly awaiting news on what ASP.NET Core holds for them (if my inbox is anything to go by). The roadmap suggested that a new version of Web Pages would be forthcoming after the initial release of .NET Core, but that item was subsequently removed. Instead, a new cross platform page-centric web development model called Razor Pages has been made available in preview form. Here, I show you how to get started with it and explore its similarities with Web Pages. In future articles, I will take a closer look at the wide range of differences.

Razor Pages is part of ASP.NET Core 2.0, which is in preview. That means that there is still development work going on and design decisions being made, so the final released version of Razor Pages could well be different to the one examined here. At the moment, ASP.NET Core 2.0 is scheduled for release some time between July and September 2017. Therefore there is plenty of time for anything described here to change. Razor Pages works with Visual Studio 2017 Update 3 which is also in preview. Aside from a code editor of some sort, you also need to install the preview of the .NET Core 2.0 SDK.

Creating the sample application

Update: The way to create a Razor Pages application in VS 2017 Update 3 Preview is to select ASP.NET Core Web Application as the project type, and then to change the target framework to .NET Core 2.0 in the selector on the next wizard screen:

.NET Core 2.0

I couldn't find a way to get VS 2017 Update 3 to generate a Razor Pages application out of the box (I could eventually, see above, but I'll leave these steps here anyway), so I resorted to using command line tools instead.

  1. First thing to do is to ensure that you have the preview of .NET Core 2.0 installed correctly, so open a command prompt (cmd.exe, Powershell, bash, terminal or whatever you prefer on your operating system) and type dotnet --version. The result should confirm that your are using a preview of version 2.0:

    ASP.NET Core 2.0

     

  2. Create a folder for the Razor Pages application:

    ASP.NET Core 2.0

  3. Navigate to the folder and type dotnet new pages followed by dotnet restore (even though the feedback in the terminal suggests that it has already been successfully run), then dotnet build. These commands generate the files for the project, retrieve referenced packages, and then build the application.

    You should be able to see the files and folders for the application in the file explorer:

    Sample application

  4. Open Visual Studio 2017 Update 3 and use the File... Open... Project/Solution dialog to navigate to the .csproj file in your new application.

    Page files

The default location for Razor Pages is the Pages folder. The sample application contains a number of files that will look familiar to Web Pages developers. However, this framework is not Web Pages. It is built on top of the ASP.NET Core MVC framework. As such, Razor Pages has all the view-related features of ASP.NET Core MVC available to it along with a host of other features, and I will look in more detail at those in future. It also works in a different way to a Web Pages site, which used the Web Site project type. Razor Pages sites use the Web Application project type. The chief difference between them is that class files can be placed in an App_Code folder in a Web Site project, and then get compiled on first request. Class files in a Web Application project can be placed anywhere in the application directly but the app needs to be compiled before being deployed to a web server.

Some files in the Pages folder end with a .cshtml.cs extension. These class files are similar to code behind files in ASP.NET web forms in that there is a one-to-one mapping between the class file and the Razor page (or view) with the same file name. This file type however is new to Razor Pages. I will look at them a bit later.

Simple Development Model

The main driver behind the Web Pages framework was to provide a simple development model. In order to deliver on that goal, Web Pages included a lot of "helpers" that hid a lot of bare wires. These helpers included the data access library exposed by the Database class that relied on the dynamic type (with its pros and cons) and the WebSecurity helper that wrapped the SimpleMembership provider. There was also a range of extension methods, IsPost, IsEmpty, AsInt and so on, that provided shortcuts to commonly used methods. None of these exist in Razor Pages, but it is still possible to adopt a Web Pages style of programming where the processing code is included in a code block at the top of the .cshtml file. Here is a Razor Page called Form.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 bulk of the code above for a simple form looks just like it would in a Web Pages page. The chief differences are that the Razor Page requires the @page directive at the top of the file. The HasFormContentType property is used to determine whether a form has been posted (instead of the IsPost method from Web Pages) and the specific collection (Form) from the Request object is referenced unlike in other versions of ASP.NET including Web Pages where the shortened Request["name"] would work.

A Better Way To Do Things

The above code will work and kind of follows the old Web Pages maxim of reducing the concept count for learners. However, where possible it is advisable to adopt a strongly typed approach to development which relies on the compiler telling you about your typing mistakes at design time, rather than at run time. Razor Pages leverages the MVC model binding framework to help with this. Model Binding is a core part of MVC which takes values from an HTTP request and maps them to a specified "Model". The model can be a simple property such as an int or string, or it can be complex such as a user-defined class.

Here's the same form from above with the use of Model Binding for the "name" value:

@page
@using Microsoft.AspNetCore.Mvc.RazorPages

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

    public PageResult OnPost()
    {
        return Page();
    }
}

<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>

Anyone familiar with Web Pages will recognise the @functions block. In this case, it is used to define a public property (string Name) and to specify is as the model for binding via the BindProperty attribute. The @Functions block is also used to specify what should happen in the Razor Page's OnPost method. In this case, the page is re-displayed with the incoming form values having been bound to the model. The OnPost method is called by the framework when the HTTP POST verb has been used for the request. To execute logic when a GET request is made, you would use the OnGet method.

The benefit of taking a strongly typed approach is that you get Intellisense support for properties in the Page:

Intellisense

So far, all code has been confined to Razor (.cshtml) files. These support Just-In-Time compilation, which means that you can mimick the Web Site project development model to a large extent. Once you have deployed your compiled application, you can replace existing Razor pages with amended ones without having to pre-compile anything and they will be compiled on demand.

The PageModel

The PageModel is the code behind file that I mentioned earlier. This allows you to move the processing code out of the Razor Page into a separate class file. This approach facilitates advanced scenarios such as unit testing the code for the view. The next iteration shows how to use the PageModel. It begins with a revised version of the Razor Page:

@page
@model FormModel

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

All of the model definition and processing code has been replaced with an @model directive (familiar to anyone who has worked with MVC). It exposes the model type to the Page. The other code has been moved to new file: Form.cshtml.cs:

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


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

        public PageResult OnPost()
        {
            return Page();
        }
    }
}

The class is named after the page file itself, in this case FormModel. It implements PageModel, a class that acts like a Controller class in MVC. This approach is generally recommended above all others. It is less likely to lead to code bloat in a page, and will make migrating to MVC a lot easier, if that is a likely future requirement.

Data Access And Membership

As I mentioned earlier, there is no Razor Pages-specific data access technology similar to the Web Pages WebMatrix.Data.Database class. The recommended data access technology is Entity Framework Core. That is not to say, however that you cannot use any other data access technology that works with .NET Core, including plain ADO.NET. The same goes for managing users. There is no Razor Pages specific approach. You would be expected to use the ASP.NET Core Identity framework, which leverages Entity Framework, or (not recommended) to roll your own solution.

Summary

This completes a first look at the Razor Pages framework. There are obvious similarities with the old Web Pages framework, but there are significant differences. Some of these differences add significant complexity, and this brief look has barely scratched the surface of those yet. It is important to highlight some of the goals behind the Razor Pages framework to put it in context:

  • Simplify the code required to implement common page-focused patterns, e.g. dynamic pages, CRUD, PRG, etc.
  • Use and expose the existing MVC primitives as much as possible
  • Allow straightforward migration to traditional MVC structure

And its only fair to repeat a couple of the non-goals - i.e. things that the framework developers specifically want to avoid doing:

  • Create a scripted page framework to compete with PHP, etc.
  • Create new primitives that are only applicable to Razor Pages

If you were looking for something that directly replaced the simplicity of Web Pages, you are likely to be disappointed. On the other hand, the additional power provided by the fact that this framework sits on top of MVC will help you to build much more robust and maintainable applications.