ASP.NET Razor Pages vs MVC: How Do Razor Pages Fit in Your Toolbox?

Matt Watson Developer Tips, Tricks & Resources Leave a Comment

As part of the release of .NET Core 2.0, there are also some updates to ASP.NET. One of those is the addition of a new web framework for creating a “page” without the full complexity of ASP.NET MVC. New Razor Pages are a slimmer version of the MVC framework and in some ways an evolution of the old “.aspx” WebForms.

In this article, we are going to cover some of the finer points of using ASP.NET Razor Pages vs MVC.

  • The basics of Razor Pages
  • ASP.NET MVVM vs MVC
  • Pros and cons on Razor Pages
  • Using Multiple GET or POST Actions via Handlers
  • Why you should use Razor Pages for everything
  • Code comparison of ASP.NET Razor Page vs MVC

The Basics: What are ASP.NET Razor Pages?

A Razor Page is very similar to the view component that ASP.NET MVC developers are used to. It has all the same syntax and functionality.

The key difference is that the model and controller code is also included within the Razor Page itself. It is more an MVVM (Model-View-ViewModel) framework. It enables two-way data binding and a simpler development experience with isolated concerns.

Here is a basic example of a Razor Page using inline code within a @functions block. It is actually recommended to put the PageModel code in a separate file. This is more akin to how we did code behind files with ASP.NET WebForms.

@page
@model IndexModel
@using Microsoft.AspNetCore.Mvc.RazorPages

@functions {
    public class IndexModel : PageModel
    {
        public string Message { get; private set; } = "In page model: ";

        public void OnGet()
        {
            Message += $" Server seconds  { DateTime.Now.Second.ToString() }";
        }
    }
}

<h2>In page sample</h2>
<p>
    @Model.Message
</p>

We Have Two Choices Now: ASP.NET MVVM or MVC

You could say that we now have the choice of an MVC or MVVM framework. I’m not going to go into all the details of MVC vs MVVM. This article does a good job of that with some examples. MVVM frameworks are most noted for two-way data binding of the data model.

MVC works well with apps that have a lot of dynamic server views, single page apps, REST APIs, and AJAX calls. Razor Pages are perfect for simple pages that are read-only or do basic data input.

MVC has been all the rage recently for web applications across most programming languages. It definitely has its pros and cons. ASP.NET WebForms was designed as an MVVM solution. You could argue that Razor Pages are an evolution of the old WebForms.

Pros and Cons of Razor Pages

I’ve been doing ASP.NET development for about 15 years. I am very well versed in all the ASP.NET frameworks. Based on my playing around with the new Razor Pages, these are my pros and cons and how I would see using them.

Pro: More organized and less magical

I don’t know about you, but the first time I ever used ASP.NET MVC I spent a lot of time trying to figure out how it worked. The naming of things and the dynamically created routes caused a lot of magic that I wasn’t used to. The fact that /Home/ goes to HomeController.Index() that loads a view file from “Views\Home\Index.cshtml” is a lot of magic to get comfortable with when starting out.

Razor Pages don’t have any of that “magic” and the files are more organized. You have a Razor View and a code behind file just like WebForms did. Versus with MVC having separate files in different directories for the controller, view, and model.

Compare simple MVC and Razor Page projects. (Will show more code differences later in this article.)

Compare MVC vs Razor Page Files

Compare MVC vs Razor Page Files

Pro: Single Responsibility

If you have ever used an MVC framework before, you have likely seen some huge controller classes that are filled with many different actions. They are like a virus that grows over time as things get added.

With Razor Pages, each page is self-contained with its view and code organized together. This follows the Single Responsibility Principle.


Free Download

 

Using Multiple GET or POST Actions via Handlers

By default a Razor Page is designed to have a single OnGetAsync and OnPostAsync method. If you want to have different actions within your single page you need to use what is called a handler. You would need this if your page has AJAX call backs, multiple possible form submissions, or other scenarios.

So for example, if you were using a Kendo grid and wanted the grid to load via an AJAX call, you would need to use a handler to handle that AJAX call back. Any type of single page application would use a lot of handlers or you should point all of those AJAX calls to an MVC controller.

I made an additional method called OnGetHelloWorldAsync() in my page. How do I call it?

From my research there seems to be 3 different ways to use handlers:

  1. Querystring  – Example: “/managepage/2177/?handler=helloworld”
  2. Define as a route in your view: @page “{handler?}” and then use /helloworld in the url
  3. Define on your input submit button in your view. Example: <input type=”submit” asp-page-handler=”JoinList” value=”Join” />

Can learn more about multiple page handlers here.

Special thanks to those who left comments about this! Article updated!

 

Why you should use Razor Pages for everything! (maybe?)

I could make an argument that Razor Pages are the perfect solution to anything that is essentially a web page within your app. It would draw a clear line in the sand that any HTML “pages” in your app are true pages. Currently, an MVC action could return an HTML view, JSON, a file, or anything. Using Pages would force a separation between how you load the page and what services the AJAX callbacks.

Think about it, this solves a lot of problems with this forced separation.

Razor Page

HTML Views

MVC/Web API

REST API calls, SOA

This would prevent MVC controllers that contains tons of actions that are a mix of not only different “pages” in your app but also a mixture of AJAX callbacks and other functions.

Of course, I haven’t actually implemented this strategy yet. It could be terrible or brilliant. Only time will tell how the community ends up using Razor Pages.

Code Comparison of ASP.NET Razor Page vs MVC

As part of playing around with Razor Pages, I built a really simple form in both MVC and as a Razor Page. Let’s take a look at how the code compares. It is just a text box with a submit button.

Here is my MVC view:

@model RazorPageTest.Models.PageClass

<form asp-action="ManagePage">
    <div class="form-horizontal">
        <h4>Client</h4>
        <hr />
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <input type="hidden" asp-for="PageDataID" />
        <div class="form-group">
            <label asp-for="Title" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
        </div>
      
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>

Here is my MVC controller. (My model is PageClass which just has two properties and is really simple.)

   public class HomeController : Controller
    {
        public IConfiguration Configuration;

        public HomeController(IConfiguration config)
        {
            Configuration = config;
        }

        public async Task<IActionResult> ManagePage(int id)
        {
            PageClass page;

            using (var conn = new SqlConnection(Configuration.GetConnectionString("contentdb")))
            {
                await conn.OpenAsync();

                var pages = await conn.QueryAsync<PageClass>("select * FROM PageData Where PageDataID = @p1", new { p1 = id });

                page = pages.FirstOrDefault();
            }

            return View(page);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> ManagePage(int id, PageClass page)
        {

            if (ModelState.IsValid)
            {
                try
                {
                    //Save to the database
                    using (var conn = new SqlConnection(Configuration.GetConnectionString("contentdb")))
                    {
                        await conn.OpenAsync();
                        await conn.ExecuteAsync("UPDATE PageData SET Title = @Title WHERE PageDataID = @PageDataID", new { page.PageDataID, page.Title});
                    }
                }
                catch (Exception)
                {
                   //log it
                }
                return RedirectToAction("Index", "Home");
            }
            return View(page);
        }
    }

Now let’s compare that to my Razor Page.

My Razor Page:

@page "{id:int}"
@model RazorPageTest2.Pages.ManagePageModel

<form asp-action="ManagePage">
    <div class="form-horizontal">
        <h4>Manage Page</h4>
        <hr />
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <input type="hidden" asp-for="PageDataID" />
        <div class="form-group">
            <label asp-for="Title" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>

Here is my Razor PageModel, aka code behind:

   public class ManagePageModel : PageModel
    {
        public IConfiguration Configuration;

        public ManagePageModel(IConfiguration config)
        {
            Configuration = config;
        }

        [BindProperty]
        public int PageDataID { get; set; }
        [BindProperty]
        public string Title { get; set; } 

        public async Task<IActionResult> OnGetAsync(int id)
        {
            using (var conn = new SqlConnection(Configuration.GetConnectionString("contentdb")))
            {
                await conn.OpenAsync();
                var pages = await conn.QueryAsync("select * FROM PageData Where PageDataID = @p1", new { p1 = id });

                var page = pages.FirstOrDefault();

                this.Title = page.Title;
                this.PageDataID = page.PageDataID;
            }

            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int id)
        {

            if (ModelState.IsValid)
            {
                try
                {
                    //Save to the database
                    using (var conn = new SqlConnection(Configuration.GetConnectionString("contentdb")))
                    {
                        await conn.OpenAsync();
                        await conn.ExecuteAsync("UPDATE PageData SET Title = @Title WHERE PageDataID = @PageDataID", new { PageDataID, Title });
                    }
                }
                catch (Exception)
                {
                   //log it
                }
                return RedirectToPage("/");
            }
            return Page();
        }
    }

Deciphering the comparison

The code between the two is nearly identical. Here are the key differences:

  • The MVC view part of the code is exactly the same except the Razor Page has “@page” in it.
  • ManagePageModel has OnGetAsync and OnPostAsync which replaced the two MVC controller “ManagePage” actions.
  • ManagePageModel includes my two properties that were in the separate PageClass before.

In MVC for an HTTP POST, you pass in your object to the MVC action (i.e. “ManagePage(int id, PageClass page)”). With a Razor Page, you are instead using two-way data binding. To get Razor Pages to work correctly with two-way data binding I had to annotate my two properties (PageDataID, Title) with [BindProperty]. My OnPostAsync method only has a single input of the id since the other properties are automatically bound.

Will it Prefix?

Do Razor Pages work with Prefix? Yes! ASP.NET Razor Pages is supported by our free ASP.NET Profiler, Prefix. Our Retrace and Prefix products have full support for ASP.NET Core.

Summary

I really like Razor Pages and can definitely see using them in an ASP.NET Core project I am working on. I like the idea of Razor Pages being the true pages in my app and implementing all AJAX/REST API functions with MVC. I’m sure there are also other use cases that Razor Pages don’t work for. The good news is MVC is super flexible, but that is what also makes it more complex. The true beauty of Razor Pages is their simplicity.

References:

About Matt Watson

Matt is the Founder & CEO of Stackify. He has been a developer/hacker for over 15 years and loves solving hard problems with code. While working in IT management he realized how much of his time was wasted trying to put out production fires without the right tools. He founded Stackify in 2012 to create an easy to use set of tools for developers.