Skip to main content

Handle Ajax Requests in ASP.NET Core Razor Pages

Razor Pages are a new feature of ASP.NET Core that makes coding page-focused scenarios easier and more productive. Razor pages use handler methods to deal with the incoming HTTP request (GET/POST/PUT/Delete). These are similar to Action methods of ASP.NET MVC or WEB API. Razor Pages follow particular naming convention and that is also true for Handler methods. They also follow particular naming conventions and prefixed with “On”: and HTTP Verb like OnGet(), OnPost() etc. The handler methods also have asynchronous version: OnGetAsync(), OnPostAsync() etc. Calling these handler methods from jQuery Ajax is tricky. This post talks how to handle Ajax requests in ASP.NET Core Razor Pages.

Handle Ajax Requests in ASP.NET Core Razor Pages

Before we look into handling Ajax requests in ASP.NET Core Razor Pages, it’s important to understand how handler methods work. BTW, if you are new to ASP.NET Core Razor Pages, following articles will help.

As mentioned earlier, the handler method follows a pattern. They are prefixed with “On” and the name of HTTP verb like,

  • OnGet
  • OnPost
  • OnPut
  • OnGetAsync
  • OnPostAsync
  • OnPutAsync

The OnGet method gets called on the page load and onPost gets called when the form gets submitted. The default template for ASP.NET Core 2.0 web application comes with a couple of razor pages. When you open About.cs.html file, you should see the following code.

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

The onGet() gets called when the request comes for the About page. Besides these default Handlers, we can also specify custom names. The custom name must come after the followed naming convention like,

  • OnGetCountries()
  • OnPostUserMaster()
  • OnPostUserDetails()

In case of multiple POST handlers on the same page, how do you call them? You need to use asp-page-handler Tag Helper and assign the handler name. Like,

<form asp-page-handler="usermaster" method="post">
    <input type="submit" id="btnSubmit" value="Save Master" />
</form>
<form asp-page-handler="userdetail" method="post">
    <input type="submit" id="btnSubmit" value="Save Details" />
</form>

Or, we can achieve the same thing with one form, and two submit inputs inside of that form:

<form method="post">
   <input type="submit" asp-page-handler="usermaster" value="Save Master" />
   <input type="submit" asp-page-handler="userdetail" value="Save Details" />
</form>

That’s enough for quick understanding. Read Razor Pages – Understanding Handler Methods for detailed information about handler methods.

Making Ajax Requests in ASP.NET Core Razor Pages

Now, let talk about calling the handler methods from jQuery Ajax. You must be thinking what is so different. Here is a GET handler method defined in “Demo” razor page.

public JsonResult OnGetList()
{
    List<string> lstString = new List<string>
    {
        "Val 1",
        "Val 2",
        "Val 3"
    };
    return new JsonResult(lstString);
}

The HTML contains only div element without a form tag.

<div id="dvItems" style="font-size:24px;">
</div>

The following jQuery code will call the OnGetList handler method available in Demo razor page and populate the list.

$.ajax({
    type: "GET",
    url: "/Demo/OnGetList",
    contentType: "application/json",
    dataType: "json",
    success: function (response) {
        var dvItems = $("#dvItems");
        dvItems.empty();
        $.each(response, function (i, item) {
            var $tr = $('<li>').append(item).appendTo(dvItems);
        });
    },
    failure: function (response) {
        alert(response);
    }
});

But, you will be surprised to see 404 not found error. See below screenshot.
Handle Ajax requests in ASP.NET Core Razor PagesTo understand the reason for getting 404 error, we need to see how the handler methods are rendered. In one the earlier code sample, we created 2 forms and called 2 handler methods. Following is the output of generated HTML on the client side. For now, ignore the __RequestVerificationToken and look at the value of the action attribute of form tag.

<form method="post" action="/Demo?handler=usermaster">
    <button class="btn btn-default">Save Master</button>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8KW5cuB058RCnNyZSLI7AUjUAtTwe54jQ4Z9Goyn3WKPcpVFYSFUM5J-JDFC3E-MZIUcyR0UnbrvrC_sHv6MbUONStuIMhqDc7i00pQiGkrzf3hK6t5gZFVrjUpyAcargow4zvKU_ISjdPfoLTNF588" /></form>

<form method="post" action="/Demo?handler=userdetail">
    <button class="btn btn-default">Save Details</button>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8KW5cuB058RCnNyZSLI7AUjUAtTwe54jQ4Z9Goyn3WKPcpVFYSFUM5J-JDFC3E-MZIUcyR0UnbrvrC_sHv6MbUONStuIMhqDc7i00pQiGkrzf3hK6t5gZFVrjUpyAcargow4zvKU_ISjdPfoLTNF588" /></form>

The thing to notice here is, the name of the handler is added to the form’s action as a query string parameter. We need to use similar URL pattern while making the jQuery Ajax call. Like,

$.ajax({
    type: "GET",
    url: "/Demo?handler=List",
    contentType: "application/json",
    dataType: "json",
    success: function (response) {
        var dvItems= $("#dvItems");
        dvItems.empty();
        $.each(response, function (i, item) {
            var $tr = $('<li>').append(item).appendTo(dvItems);
        });
    },
    failure: function (response) {
        alert(response);
    }
});

Now, this should work. You can use the same approach for POST requests as well. Here is a POST handler method defined in “Demo” razor page.

public ActionResult OnPostSend()
{
    List<string> lstString = new List<string>
    {
        "Val 1",
        "Val 2",
        "Val 3"
    };
    return new JsonResult(lstString);
}

The HTML contains only div element without a form tag.

<div id="dvPostItems" style="font-size:24px;">
</div>

Here is jQuery Ajax call for the POST method.

$.ajax({
    type: "POST",
    url: "/Demo?handler=Send",
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function (response) {
        var dvItems = $("#dvPostItems");
        dvItems.empty();
        $.each(response, function (i, item) {
            var $tr = $('<li>').append(item).appendTo(dvItems);
        });
    },
    failure: function (response) {
        alert(response);
    }
});

Bang!!! This gives you a 400 bad request error.
Handle Ajax requests in ASP.NET Core Razor PagesYou must be wondering now, what’s wrong with the above code. Well, there is nothing wrong with the above code. The reason is,

Razor Pages are designed to be automatically protected from cross-site request forgery (CSRF/XSRF) attacks. You don’t have to write any additional code. Antiforgery token generation and validation is automatically included in Razor Pages. Here the request fails, there is no AntiForgeryToken present on the page.

There are 2 ways to add the AntiForgeryToken.

  • In ASP.NET Core MVC 2.0 the FormTagHelper injects anti-forgery tokens for HTML form elements. For example, the following markup in a Razor file will automatically generate anti-forgery tokens:
    <form method="post">
      <!-- form markup -->
    </form>
    
  • Add explicitly using @Html.AntiForgeryToken()
  • To add AntiForgeryToken, we can use any of the approaches. Both the approaches add an input type hidden with name __RequestVerificationToken. The Ajax request should send the anti-forgery token in request header to the server. So, the modified Ajax request looks like,

    $.ajax({
        type: "POST",
        url: "/Demo?handler=Send",
        beforeSend: function (xhr) {
            xhr.setRequestHeader("XSRF-TOKEN",
                $('input:hidden[name="__RequestVerificationToken"]').val());
        },
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: function (response) {
            var dvItems = $("#dvPostItems");
            dvItems.empty();
            $.each(response, function (i, item) {
                var $tr = $('<li>').append(item).appendTo(dvItems);
            });
        },
        failure: function (response) {
            alert(response);
        }
    });
    

    Since the script sends the token in a header called X-CSRF-TOKEN, configure the antiforgery service to look for the X-CSRF-TOKEN header:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
        services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
    }
    

    Now, when the Post request is executed, it should work as expected. Below is another working example of POST request, sending data from client to server.

    $('#btnPost').on('click', function () {
        var item1 = $('#txtItem1').val();
        var item2 = $('#txtItem2').val();
        var item3 = $('#txtItem3').val();
        $.ajax({
            type: "POST",
            url: "/Demo?handler=Send",
            beforeSend: function (xhr) {
                xhr.setRequestHeader("XSRF-TOKEN",
                    $('input:hidden[name="__RequestVerificationToken"]').val());
            },
            data: JSON.stringify({
                Item1: item1,
                Item2: item2,
                Item3: item3
            }),
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function (response) {
                var dvItems = $("#dvPostItems");
                dvItems.empty();
                $.each(response, function (i, item) {
                    var $tr = $('<li>').append(item).appendTo(dvItems);
                });
            },
            failure: function (response) {
                alert(response);
            }
        });
    })
    

    Here, we can’t access the passed data via query string as the querystring contains “?handler=Send” value. Hence, to access the passed value on the server, read the Request object’s body. Like,

    public ActionResult OnPostSend()
    {
        string sPostValue1 = "";
        string sPostValue2 = "";
        string sPostValue3 = "";
        {
            MemoryStream stream = new MemoryStream();
            Request.Body.CopyTo(stream);
            stream.Position = 0;
            using (StreamReader reader = new StreamReader(stream))
            {
                string requestBody = reader.ReadToEnd();
                if(requestBody.Length > 0)
                {
                    var obj = JsonConvert.DeserializeObject<PostData>(requestBody);
                    if(obj != null)
                    {
                        sPostValue1 = obj.Item1;
                        sPostValue2 = obj.Item2;
                        sPostValue3 = obj.Item3;
                    }
                }
            }
        }
        List<string> lstString = new List<string>
        {
            sPostValue1,
            sPostValue2,
            sPostValue3
        };
        return new JsonResult(lstString);
    }
    

    The above code also deserialize the JSON data into PostData class object and below is the definition of PostData class.

    public class PostData
    {
        public string Item1 { get; set; }
        public string Item2 { get; set; }
        public string Item3 { get; set; }
    }
    

    You can find the source code in the Github.

    Summary

    The post talks about ASP.NET Core Razer Pages handler methods naming conventions and creating named handler method. Razor Pages are designed to be protected from (CSRF/XSRF) attacks. Hence, Antiforgery token generation and validation are automatically included in Razor Pages. The post also provide solutions for making Ajax requests for Razor pages handler methods.

    Thank you for reading. Keep visiting this blog and share this in your network. Please put your thoughts and feedback in the comments section.

    PS: If you found this content valuable and want to return the favour, then Buy Me A Coffee

    41 thoughts to “Handle Ajax Requests in ASP.NET Core Razor Pages”

    1. 1: in view Model:

      public class IndexModel : PageModel
      {
      [BindProperty]
      public string OneBindingData { get; set; } <-binding data

      public JsonResult OnPostTest()
      {
      Console.WeiteLine(OneBindingData); <-recived data ok

      …..
      return new JsonResult(new LIst(){“one”, “two”, …})
      }
      }

      2: in view:

      Submit

      3. ajax:

      const inputText = documet.getElementById(‘input-post’);
      inputText.val = “data to send”;
      const form = documet.getElementById(‘my-form-post’)

      fetch(‘/Index?handler=Test’, {
      method:’post’,
      body:new FormData(form)

    2. Thanks, but this shorter version also works:

      1: in view Model:

      public class IndexModel : PageModel
      {
      [BindProperty]
      public string OneBindingData { get; set; } <-binding data

      public JsonResult OnPostTest()
      {
      Console.WeiteLine(OneBindingData); <-recived data ok

      …..
      return new JsonResult(new LIst(){“one”, “two”, …})
      }
      }

      2: in view:

      Submit

      3. ajax:

      const inputText = documet.getElementById(‘input-post’);
      inputText.val = ‘data to send’;
      const form = documet.getElementById(‘my-form-post’)

      fetch(‘/Index?handler=Test’, {
      method:’post’,
      body:new FormData(form)

    3. Hello , how to set url when place a call within identity area and need to call something like this, identity/account/manage/index?handeler=onpostdata()

    4. Hello
      i am trying to call a post method but every time i am recieving null data. and i am trying post string data to onpost method.

      public IActionResult OnPostUpdateJsonInfo(string UpdatedJsonObject)
      {
      return Page();
      }

      i always get the value of UpdatedJsonObject null
      AJAX CALL
      $.ajax({
      url: ‘/UpdateJsonInfo’,
      type: ‘POST’,
      contentType: ‘application/json; charset=utf-8’,
      beforeSend: function (xhr) {
      xhr.setRequestHeader(“XSRF-TOKEN”,
      $(‘input:hidden[name=”__RequestVerificationToken”]’).val());
      },
      dataType: ‘json’,
      data: JSON.stringify(locsInfo),
      success: (data) => {
      alert(data);
      window.location.reload();
      },
      error: (error) => {
      alert(error);
      }
      });

      locsInfo is the json array

      can you please help me?

    5. Excellent article. I’ve replicated your examples (with minor changes) in ASP.NET Core 3.1, and it all works. I’m new to ASP.NET Core and Razor, but experienced otherwise, and it’s really refreshing to see a tech article that doesn’t either assume knowledge of vital details, and therefore omit them, or be riddled with errors, or both. Or be so out of date as to be useless. I was slightly nervous when I saw you’d done this with Core 2, but all good.

      I shall buy you a coffee – thank you.

        1. I wonder if I could ask a supplementary question, now that I’ve been using the technique you describe in this article for a little while.

          How would you redirect to a different page at the end of the handler? As the Ajax call returns to the JavaScript function specified in the ‘success’ argument, it could be done client-side if the target is passed down in the response, but it would be nice to be able to use RedirectToPage or something like it.

          Is this possible?

    6. Hi,

      Thanks for this.

      I am trying to call a get function from my view model but am getting the view html for the page instead of the json result?

      Below is my code:
      Javascript
      $(“#selectAccountDropDown”).change(function () {

      $.ajax({
      contentType: “application/json;charset=utf-8”,
      type: “GET”,
      dataType: “json”,
      url: ‘Forms?RepairsForm/?handler=Test’,
      success: function (result) {
      alert(“Data is received” + dataR);
      },
      error: function (result) {
      alert(“The error msg is” + result.responseText);
      }
      })
      })

      View model function
      [HttpGet]
      public static IActionResult OnGetTest()
      {
      string t = “Test”;
      return new JsonResult(t);
      }

      view

      Choose

      Second Number:

      Any ideas?

      Thanks in advance.

    7. Guys Sorry, The example is working it was my bad, I made a mistake inside my start up file my Anti @Html.AntiForgeryToken() was not set up in good way. There wwas a space inside the string which I did not observe it.

    8. Thank you for posting this. However, It never worked for me. I need to finish some stuff at my work. Please could you help me and give me some insight.
      this is my action which I need to post some data to it in order to be saved in data base
      public async Task OnPostAdd([FromBody] Education obj)
      {
      if (!ModelState.IsValid)
      {
      return Page();
      }
      return new JsonResult(“Customer Added Successfully!”);
      }
      this is my ajax code to hit the action

      var options = {};
      options.url = “/EducationPage?handler=Add”;
      options.type = “POST”;

      var obj = {};
      obj.eduId = $(“#eduId”);
      obj.userId = $(“#userId”);
      obj.instName = $(“#instName”).val();
      obj.certName = $(“#certName”).val();
      obj.major = $(“#major”).val();
      obj.startDate = $(“#startDate”).val();
      obj.endtDate = $(“#endDate”).val();
      obj.descrp = $(“#descp”).val();
      console.log(obj);
      options.data = JSON.stringify(obj);
      // options.data = $(“#myForm”).serialize;
      //console.log(options.data)
      options.contentType = “application/json; charset=utf-8”;
      options.dataType = “json”;

      options.beforeSend = function (xhr) {
      xhr.setRequestHeader(“MY-XSRF-TOKEN”,
      $(‘input:hidden[name=”__RequestVerificationToken”]’).val());
      };
      options.success = function (msg) {
      $(“#msg”).html(msg);
      };
      options.error = function () {
      $(“#msg”).html(“Error while making Ajax call!”);
      };
      console.log(options);
      $.ajax(options);
      });
      When I test , I am having the data but it never hits the back end , I appreciate your help and time.

    9. Hi,

      On post I am creating adding some more entities in the binding list , and returning the razor page. It works fine.
      I have another option where user can remove some rows from the list, based on the selected rows to remove , am creating new list with remaining rows.
      But on the page it removes rows but it keeps the data which supposed to remove.

      Let say there are 10 rows and if I remove 5th and 6th row , it removes last two rows and it keeps 5th and 6th row.
      After removing those rows on server side it shows the list as expected but on the screen it does not.

      Can you please help me.

      Thanks,
      Bapusaheb

    10. Great article! Really explained why I was beating my head against the wall when things were not working as I would have expected.

    11. Regarding data in the request.
      Surprisingly in a GET request you can add data as follows:
      data: { ‘myParam’: myval },
      and they are received correctly on the the asp.net method parameter named myParam.
      That doesn’t work with a POST.
      Any idea?

    12. What about an ajax Get request where you receive a 401, unauthorized access. How can I redirect the user to the login page as it never makes it to the [Authorize] controller/method?

      1. John,

        Create a login controller and decorate with AllowAnnoymous attribute to get the token once authenticated. Later on use this token for authorized API calls.

      2. Couldn’t you handle a simple page redirection within the Ajax request itself? You could call it from within the Ajax request’s designated failure function and set a condition for that specific status code.

    13. 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?

      1. It should work. Make sure to check following things in your code.
        1. The API should return JSON data.
        2. In your ajax code, make sure to set “content-type” and “data” attribute properly (as shown in the post).

    14. Hi, thanks for nice article. I am learning razor pages, my question is : If we can use handlers to access server side code then why would we need to use the ajax calls. Since the same thing can be achieved using page handlers.

      Thank you

    Leave a Reply

    Your email address will not be published. Required fields are marked *