ASP.NET Core Razor Pages: How to implement AJAX requests

Last Updated: May 1, 2018 | Created: April 2, 2018

ASP.NET Core 2.0 introduced a new way to build a web site, called Razor Pages. I really like Razor Pages, but I needed to work out how to do a few things. In this article I describe how to handle AJAX requests. It turns out there are two ways to implement the C# code that handles HTTP requests that return json or other data.

TL;DR – summary

ASP.NET Core’s Razor Pages normally deliver HTML pages, but there is still the need to deliver data for AJAX requests. This can be done in two ways: via what is known as a named page handler, or by using a normal razor page.

I describe two approaches, but I think the second is better. I also cover how to handle HTTP POST requests via AJAX, as there is need to add the anti-forgery token to the request.

Who should read this

This article is focused on how to handle AJAX requests in Razor Pages, so if that is your thing then this is useful to you.

To keep this article short, I assume you know C# and have some idea of what the ASP.NET Core framework does. I do give a very brief introduction to Razor Pages, but there are much better overviews elsewhere.

The code in this article is focused on ASP.NET Core 2.0 or above, as that is the point where Razor Pages was introduced.

NOTE ASP.NET Core documentation page “Introduction to Razor Pages in ASP.NET Core” is a great overview of what Razor Pages. The site https://www.learnrazorpages.com/ is also a good resource to look at.

A very brief introduction to Razor Pages

The classic way to deliver web pages in ASP.NET Core and ASP.NET MVC is via the controller->actions->views approach. The controllers contained many methods (known as actions) which dealt with specific parts of the URL. For instance, the URL /Orders/Index would run the method called Index in the controller called OrdersController.

Razor pages provides a different, simpler (better!) approach. The controller has gone, and the action methods move into their own, per-URL class in a directory. For instance, with Razor Pages, the URL /Orders/Index would look for a Razor Pages view named Index in the directory called Orders.

The Razor Pages view for the /Order/Index consists of two files:

  1. Order/Index.cshtml – the Razor Pages’ view that converts to HTML (must be there)
  2. Order/Index.cshtml.cs – the Razor Pages’ PageModel, which contains the code (optional)

The PageModel file is optional, but if you have any amount of C# code then this file is the place to put it. The only reason for not having a PageModel file is if your page static (i.e. no dynamic data), or the code is so simple you can write it using razor syntax. (Razor syntax is quite powerful now, with functions etc, but I still think the PageModel file is the right place for code to put the was typically written in the controller’s action methods before Razor Pages came alone).

What I really like about Razor Pages is they follow the Single Responsibility Principle (SRP) in that the two files deal with a specific URL and nothing else – they are very focused and meet the S.O.L.I.D. principles  of software design.

Here are two example files that implement the About page in the default Razor Pages application. This shows how they focus on one thing – the About page.

About.cshtml

 
@page 
@model AboutModel 
@{ 
    ViewData["Title"] = "About"; 
}

<h2>@ViewData["Title"]</h2>


<h3>@Model.Message</h3>


Use this area to provide additional information.

About.cshtml.cs

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPageApp.Pages
{
    public class AboutModel : PageModel
    {
        public string Message { get; set; }

        public void OnGet()
        {
            Message = "Your application description page.";
        }
    }
}

The problem I am trying to solve – AJAX requests

In any web application you are bound to come across places where you want to fill in or change some data on the page without doing a whole refresh (see this intro to AJAX). AJAX really helps in making a fast, user-friendly web interface.

In my case I had an application with a filter where the user can choose how they wanted to filter a list of books (see the live http://efcoreinaction.com/ example site). Once the user has selected the filter type I use an AJAX request to get the values to put in the “Filter By” dropdownlist (greyed out in the figure below because the user hasn’t yet selected the filter type).

I know how to do this in the classic controller->action->view approach (you add another action to your controller), but how do you do it with Razor Pages?

It turns out there are two ways, and I will describe both (hint: but I like the second way more!). Then I will look at dealing with AJAX requests that use a HTTP POST, as there are extra security issues to handle.

NOTE that my examples work for AJAX but they would also work for implementing WebAPI for say for an external application. But ASP.NET Core with swagger is most likely a better solution for full-on WebAPIs.

Solution 1: Using named page handlers

In a typical PageModel file you have named methods called OnGet, OnPut, OnGetAsync etc. These deal with the different types of HTTP request to the URL defined by the folder the Razor Page is in, and the name of the Razor Page. But there is a way to define a call to a method outside of this naming convention, called named page handlers. The example given by Microsoft is providing different options for a particular page, say two different ways to register a user. But you can use it for AJAX calls too.

The default format for handlers is to add “?handler=YourMethodName” to the URL. So my first solution was to alter my JQuery ajax call as shown below:

$.ajax({
   url: '/?handler=Filter',
   data: {
       FilterBy: filterByValue
   }
})
.done(function(result) {
   //… rest of code left out

Then I changed my Index.cshtml.cs file to look like this

public class IndexModel : PageModel
{
    private readonly IListBooksService _listService;
    private readonly IBookFilterDropdownService _filterService;

    public IndexModel(IListBooksService listService, 
        IBookFilterDropdownService filterService)
    {
        _listService = listService;
        _filterService = filterService;
    }

    public SortFilterPageOptions SortFilterPageData { get; private set; }
    public IEnumerable<BookListDto> BooksList { get; private set; }

    public void OnGet(SortFilterPageOptions options)
    {
        BooksList = _listService
            .SortFilterPage(options)
            .ToList();

        SortFilterPageData = options;
    }

    public JsonResult OnGetFilter(BooksFilterBy filterBy)
    {
        return new JsonResult(_filterService.GetFilterDropDownValues(filterBy));
    }
}

The last four lines (the OnGetFilter method) are the ones to focus on. This is the named page handler with the name Filter, and I am picking up a HTTP GET request (I talk about HTTP POST AJAX request later).

This works fine, but it has made my Index PageModel not follow the Single Responsibility Principle, in that its dealing with two types of request. Hence, I looked for a more SOLID design, which I cover next

Solution 2: A dedicated Razor Page just for the AJAX request

While a razor page normally displays HTML there is no reason why its handles methods can’t return json, or any other data type. I therefore made myself a dedicated Razor Page for my filter. Here is the code:

Filter.cshtml

This is the razor page

@page
@model RazorPageApp.Pages.FilterModel
@{
    ViewData["Title"] = "Filter";
}
<h2>Filter</h2>

You must have a .cshtml file, as this sets up the routing. By trial and error, I found you must have the first two lines, but you don’t need the rest.

 Filter.cshtml.cs

The code now just contains the filter code, which is much clearer.

public class FilterModel : PageModel
{
    private readonly IBookFilterDropdownService _filterService;

    public FilterModel(IBookFilterDropdownService filterService)
    {
        _filterService = filterService;
    }

    public JsonResult OnGet(BooksFilterBy filterBy)
    {
        return new JsonResult(_filterService.GetFilterDropDownValues(filterBy));
    }
}

And my ajax call now has a url of  ‘/Filter’, as seen below

$.ajax({
   url: '/Filter',
   data: {
       FilterBy: filterByValue
   }
})
.done(function(result) {
   //… rest of code left out

I think this is much more SOLID. The filter has its own PageModel which just deals with the filter and my Index PageModel isn’t ‘polluted’ with the AJAX filter code. I do need a Filter.cshtml to define the routing, but its pretty simple (and the ASP.NET Core default .cshtml is fine). This is my chosen solution because its much simpler and easy to change without any side-effects.

But I’m not finished yet, because we need to handle POST requests with the XSRF/CSRF security feature.

How to handle POST requests with the XSRF/CSRF security feature

Razor Pages provide an anti-forgery token/verification to stop possible security issues in HTTP POST page requests, such as forms (hurrah! – safe by default). The anti-forgery token (you may know it as the [ValidateAntiForgeryToken] attribute) stops cross-site request forgery (known as XSRF or CSRF).

However, for any AJAX POST we must provide the anti-forgery token ourselves. There are two parts to this:

1. Make sure the anti-forgery token is in the page where your AXJAX request comes from

Razor will create right token, if you ask it to, but there is no default for AJAX. Therefore, you need to do something to get razor to add the anti-forgery token. The easiest is to use the Html helper @Html.AntiForgeryToken(), which will add the token. But if you already have a form tag which contains method=”post” then razor will have already added the anti-forgery token.

In my case I had a form, but it used the default HTTP GET, so I had to add the  @Html.AntiForgeryToken() code to my razor page.

NOTE: Microsoft documentation on anti-forgery token in JavaScript shows another way, by adding a function, but I am used to getting the value using JQuery. Either works, but I show the JQuery way.

2. Add the anti-forgery token to your request data

You need to add the anti-forgery token to your JQuery AJAX request. The default name that ASP.NET Core is looking for is RequestVerificationToken in the request header, and the value should be the anti-forgery token. Here is my modified JavaScript to add this.

$.ajax({
   url: '/Filter',
   type: 'POST',
   data: {
       FilterBy: filterByValue
   },
   headers: {
       RequestVerificationToken: 
           $('input:hidden[name="__RequestVerificationToken"]').val()
})
.done(function(result) {
   //… rest of code left out

My approach uses JQuery to find the hidden anti-token input tag and extract the value to send back to ASP.NET Core. Having done that your AJAX POST will work – if you got either of those wrong you will get a status 400 on your POST AJAX request.

Handling json data in POST

One of my readers,

NOTE: See Andrew Lock’s detailed article “Model binding JSON POSTs in ASP.NET Core“, which explains why this is needed, and some more details on how to use [FromBody]. Its worth reading.

Typically json returns are used to return more complex objects. In this example I use a Person class, taken from Andrew Lock’s article.  The following JavaScript code shows the ajax call that return a json content type – note that the contentType is set to json and the data has to be JSON.stringified to work.

var person = {"FirstName":"Andrew","LastName":"Lock","Age":"31"};
$.ajax({
   url: '/People',
   type = 'POST',
   contentType = 'application/json; charset=utf-8',
   headers = {
      RequestVerificationToken: 
          $('input:hidden[name="__RequestVerificationToken"]').val()
   },
   data: JSON.stringify(person)
})
.done(function(result) {
   //… rest of code left out

The json data will be returned into a class, in this case a Person class, as shown below.

public class Person  
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

And here is a simple Razor PageModel that takes the json data and binds it to a new instance of that Person class.

public class PeopleModel : PageModel
{
    public void OnPost([FromBody]Person person)
    {
        //do something with the person class
    }
}

NOTE: The Microsoft Binding documentation points out that you can only have one property or parameter with the [FromBody] attribute, as the binding reads the input stream. This means you can’t have both a property and a method parameter with the [FromBody] – if you do the parameter will be null.

Conclusion

It took me a little while to work out the best way to handle AJAX requests in Razor Pages – I even learn a few more things writing this article! But now I have an approach that works, and it follows the SOLID software principles.

I really like Razor Pages, mainly because that separate each page into its own, singled-focused Razor Page (the structure of the class controllers, with all actions mixed together, was messy and hard to work with). I may be writing more about Razor Pages in the future, so keep your eye out for more.

Happy coding.