In the last post I wrote about one of the .Net Core 2.0 features – Razor Pages. Today, we will crawl a little bit in this mud… I mean, we will write some code.
I chose a simple BMI calculator as a demo project (you can download the code from github). You know, 2 inputs and the code-behind that will tell us if we are very fat or just slightly fat or we are OK and it’s time for another cookie. Because, there is always a time for a cookie, no matter what!
A warm up
Last time we created a project from Visual Studio template so now let’s move to that project and create a brand new Razor Page.
Right-click on the Pages folder, select Add -> New Item and then Razor Pages.
Let’s name the file BMICalculator.cshtml.
We get a file that looks almost the same like a standard Asp.Net MVC Razor file. Almost!
Tl;dr
@page directive
In the first line of Razor Pages you will see a @page directive.
This ensures us that the requests from this site will be handled without engaging a controller. Remember, it’s crucial that the above directive appears in the very first line!
Model in Razor Pages
By convention, model of each page has the same name as the page. Usually, you will put it in the .cshtml.cs (or even in .cshtml, with the @function directive, if you are a real mess lover! 😛 ) file. But hey! That’s not some crucial rule you cannot break! It’s just the convention ?!
[BindProperties] attribute
If you like MVVM, you will like this attribute – it makes ASP.Net Core to bound your form data with your model. Of course you can have just one property of some custom class and mark it with [BindProperty], or you can act like me in the below example and add a few particular properties and bind it separately.
Binding your fields with the form is really a usefull thing – witout it we would have to extract all the form data right from the request.
public class BMICalculatorModel : PageModel { //… [Required] [BindProperty] public int Weight { get; set; } [Required] [BindProperty] public int Height { get; set; } //… }
TempData attribute
ASP.Net Core 2.0 brings something new, supported both on controllers and Razor Pages – the [TempData] attribute.
public class BMICalculatorModel : PageModel { [TempData] public string ResultInfo { get; set; } //… }
We just add it above a property and then use this model field like any other one:
<h2>@Model.ResultInfo</h2>
Backend logic
Finally – something meaningful! You can handle POST or GET requests just straight from your model. Let’s go back to out BMI calculator.
In Pages/BMICalculator.cshtml we have some small form:
@page @model BMICalculatorModel @{} <h2>@Model.ResultInfo</h2> <form method="post"> <div asp-validation-summary="All"></div> <div> <label asp-for="Weight"></label> <input asp-for="Weight" /> kg </div> <div> <label asp-for="Height"></label> <input asp-for="Height" /> cm </div> <br/> <button>Calculate</button> </form>
As you can see, we have here a form with method declared as POST. And… we don’t need anything else in the form tag!
We also have a button inside the form. Very simple situation.
The code behind (Pages/BMICalculator.cshtml.cs) looks like this:
public class BMICalculatorModel : PageModel { [TempData] public string ResultInfo { get; set; } [Required] [BindProperty] public int Weight { get; set; } [Required] [BindProperty] public int Height { get; set; } public void OnGet() //(1) { } public async Task<IActionResult> OnPostAsync() //(2) { if(!ModelState.IsValid) { return Page(); } //Some logic here… return RedirectToPage(); } }
As you can see, in the code-behind we can have many methods – in the above example I put only two.
//(1) – handles the GET request (which is empty)
//(2) – handles the POST.
The logic is quite simple so I will not focus on it.
What if I want to handle 2 POST requests on one page?
Seems easy in MVC, right? Don’t worry, it’s similarly easy in Razor Pages! Just take a look at NiceBMICalculator.cshtml in my github repo.
The .cshtml file looks like this:
@page @model NiceBMICalculatorModel @{ } … <form method="post"> … <input type="submit" asp-page-handler="Calculate" value="Calculate" /> <input type="submit" asp-page-handler="TellPretty" value="Just tell me that I am pretty!" /> </form>
As you can see, we have 2 input-submit controls here. Both of them has asp-page-handler that determines which backend method will be called.
To handle these two requests, we have the following code in .cshtml.cs file:
public class NiceBMICalculatorModel : PageModel { [TempData] public string ResultInfo { get; set; } [Required] [BindProperty] public double Weight { get; set; } [Required] [BindProperty] public double Height { get; set; } public void OnGet() { } public async Task<IActionResult> OnPostCalculateAsync() { //… return RedirectToPage(); } public async Task<IActionResult> OnPostTellPrettyAsync() { //… return RedirectToPage(); } }
To sum up – if we need to handle more than one requests on the same method (in this scenario – POST), we just:
- in html add a button with asp-page-handler=”SomeUniqueName”
- in code-behind write a method with name build like this On{Method_Type}{SomeUniqueName}Async.
Like in the above example:
- OnPostCalculateAsync – called when input with asp-page-handler=”Calculate” is pressed
- OnPostTellPrettyAsync – called when input with asp-page-handler=”TellPretty” is pressed
Wow, what a surprise! ?
Summary
After coding this super easy demo app I am still not convienced that Razor Pages will be a future or something. Maybe that’s because I am rather fed up with WebForms… Or just because I like too much the idea of splitting the code in MVC file structure.
However, I am curious how (or – if 😉 ) it will develop into something nice that everybody will love and use (to be honest – I hope not! 😀 ). And according to the research I have made, Razor Pages have some enthusiasts so maybe coding is like a fashion – it has to return to its starting point every couple years…
Featured image by Josh Calabrese