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

Last Updated: July 31, 2020 | 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.

PS. If you are using Razor pages you might be interested in two other articles.

0 0 vote
Article Rating
Subscribe
Notify of
guest
27 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
karan
karan
1 month ago

Hello i am trying to place an ajax under the identity area as below

/Identity/Account/Manage/Index.chtml.cs

but not able to post it.

NR
NR
3 months ago

Hello, thanks for a simple and clear explanation. How do we return a Razor Partial View? For example if we wanted a search filter that would then render a grid of results where the grid HTML is returned via AJAX. Cannot seem to find any samples. They all use MVC with model, view and controller. None seem to use the ASP.NET Core 2.0 Razor Page way.

NR
NR
3 months ago
Reply to  NR
Jon P Smith
2 years ago
Reply to  NR

Glad you got it sorted. Looking at the stackoverflow answer it’s certainly not simple!

Ray Fan
Ray Fan
3 months ago

Thank you for the post. I got ajax post to work with my razor page, but could not get the json data I’m posting to bind to the property on my razor page, is there a way to do that?

Jon P Smith
3 months ago
Reply to  Ray Fan

Hi Ray,

In this article I show the input to an AJAX call picked up as the parameter of the called method, e.g. OnGet(BooksFilterBy filterBy), which I think is the best approach.

But I *think* you are asking about getting the sent data into into a property in your razor page. If that’s your question then you need to do two things to get ASP.NET Core to bind the input to you property(s).
1. Firstly you must have the attribute [BindProperty] on every property you want to be filled in.
2. If your AJAX call is a GET then the attribute must look like this [BindProperty(SupportsGet = true)]. This is a security method that stops a property being overwritten on a GET unless you specifically ask for that.

I hope that what you are asking.

Ray Fan
Ray Fan
2 years ago
Reply to  Jon P Smith

Hi Jon, thank you so much for the reply and sorry I wasn’t clear on that, I meant when I tried to do a POST with AJAX, the model in my code was not bound. Later I got it working by putting [FromBody] on my property instead of [BindProperty].

public class IndexModel : PageModel
{
[FromBody]
public Customer Customer { get; set; }

// or
public JsonResult OnPostCreate([FromBody]Customer customer)
{}
}

Frank Monroe
Frank Monroe
3 months ago

Very well explained. Thank you!

Jon P Smith
3 months ago
Reply to  Frank Monroe

Thanks Frank for your comment – glad you liked it.

I had to dig a bit to get a ‘nice’ solution, but that helps me in the long run too.

Mattias Jonsson
Mattias Jonsson
3 months ago

Thanks for a helpful post! Can I see the code that’s left out of the javascript code? I’m trying to achieve something similar to what’s on http://efcoreinaction.com/ , but I can’t get it to work.

Jon P Smith
3 months ago

Hi Mattias,

The original JavaScript code in book app in my book can be found here. https://github.com/JonPSmith/EfCoreInAction/blob/Chapter05/EfCoreInAction/wwwroot/js/bookList.js#L37-L58 Note that the book app filter request returned a trace ident (for showing logs) and the filter value, which is accessed via indentAndResult.result.

PS. I did the same thing, i.e. changed the EfCoreInAction book app to get a proper feel for Razor Pages. I really like Razor Pages – much simpler and follows the SOLID pattern.

J-Lo
J-Lo
3 months ago

This is great! As to Janus’s question below – this is useful for anyone 1) trying to post data back to the server and/or 2) update contents on the client side from the server – without wanting the razor page to reload/refresh/redirect. As far as I know, this can only be done via AJAX, and the official netcore2.1 documentation from MS does not cover this how-to. The dedicated Razor page for handling AJAX requests was really smart!

Richard Young
Richard Young
3 months ago

I have been trying to find a way to implement an autocomplete function for text inputs that uses jquery and Razor Pages, this realy get very close to a solution since all the existing example assume you will use a controller. COuld you point me to any exmaple of autocomplete applicable to razor pages

Duge Buwembo
Duge Buwembo
3 months ago
Reply to  Richard Young

W3 Schools has a really good barebones from scratch example for an Autocomplete drop down implemented with pure Html, Css and Javascript. I adapted this example for my own project and found it was the best solution because it gave me full control of the behaviour of the autocomplete functionality. Check it out here:

https://www.w3schools.com/howto/howto_js_autocomplete.asp

You can adpat this by adding Ajax to populate the Javascript array of Name suggestions.

Richard Young
Richard Young
2 years ago
Reply to  Jon P Smith

even closer still. This is difficults for me, having read your book a dozen times in pieces I just can’t follow the get request to jquery.

public class SuppliersFilterModel : PageModel
{

private readonly Services.SuppliersList _supplierList;

public SuppliersFilterModel(SuppliersList suppliersList)
{
_supplierList = suppliersList;
}
[HttpGet]
public JsonResult OnGet([FromBody]string search)
{
return new JsonResult(_supplierList.GetFilterDropDownValues(search));
}
}

when I test the result from a page, the search value is never passed to the get parameter. I am using the example you giv eon this site. I have a single razor page with nothing in it but the basic two lines. The pagemodel about should respond to the get but the search string is never passed to the get request.

https://localhost:44353/SuppliersFilter/?search=pl is the request and it hit the action for that pages get response but no parameter are ever passed.

the jqeury is this:





the Action is getting hit, but no search test ever get in the paramerter. Does this involve the post protection in asp.net for post request?

Janus Knudsen
Janus Knudsen
3 months ago

Quite interesting. But I wonder.. I this really the intended purpose of razor and ajax? Seems a bit non-supported by MS.

Jon P Smith
3 months ago
Reply to  Janus Knudsen

Hi Janus,

I think there are times when you need ajax in an application. It’s not that MS/Razor doesn’t support it, but more that they don’t have much documentation on it – (see Feedback “Onchange event handler to ajax call?” and Rick Anderson’s comment on this page https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/search?view=aspnetcore-2.1).

That’s why I wrote this article, and its proved popular, coming third on my site this month with +3,000 page views (8% of all views)

Hassan Al-Haj
Hassan Al-Haj
3 months ago

That’s helpful, Thank you

seanhunt
seanhunt
3 months ago

This is neat. Thanks. One of my things is complicated AJAX with a Client Command Pattern to a Server Command Pattern back to another command pattern on the Client. I can’t wait to try this out. Then I have to do the same with MVC Views.

Jon P Smith
3 months ago
Reply to  seanhunt

Glad you found the article useful. This article is very popular (2nd highest page views on my site), which says AJAX is used with Razor Pages a lot.

dxm38
dxm38
3 months ago

Hi I just have a couple of things.. If the authentication token expires whilst waiting on the user after 30mins or so the ajax request fails. How would you handle that or is that a more complex scenario? Also, I purchased the book ef core in action and noticed the example site that is listed appears to be broken IIS 502.5
Thanks

Jon P Smith
3 months ago
Reply to  dxm38

I haven’t got a answer to that. Its a identity/authorization question which I haven’t looked into – sorry.

Yep, the EfCoreInAction site is down. I need to update it to 2.1/3.0 but I’m really busy at the moment. You can run the one in the EfCoreInAction repo – go to branch Chapter05 (or one with a newer EF Core, like Chapter05-NetCore3) and run it. It will auto create/seed a database and you are away!

dxm38
dxm38
3 months ago
Reply to  Jon P Smith

Excellent thanks for letting me know about the repo. Looking forward to starting the book.

Jon P Smith
3 months ago
Reply to  dxm38

http://efcoreinaction.com/ is back up – it was a pain to do as I use an inexpensive hosting provider and there were lots of changes to do to the setup.

I haven’t got the http://cqrsravendb.efcoreinaction.com/ site back up as that is likely to be even more difficult.

Rob Gaudet
3 months ago

Have been struggling with this for a while. Can you tell me how would one create an ajax request for the jquery autocomplete from a _layout page?