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.
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>
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.
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.
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.)
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.
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:
Can learn more about multiple page handlers here.
Special thanks to those who left comments about this! Article updated!
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.
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(); } }
The code between the two is nearly identical. Here are the key differences:
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.
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.
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:
Stackify's APM tools are used by thousands of .NET, Java, PHP, Node.js, Python, & Ruby developers all over the world.
Explore Retrace's product features to learn more.
If you would like to be a guest contributor to the Stackify blog please reach out to [email protected]