8

I've found examples of have multiple handlers on a page and the associated naming convention (ie OnPostXXX) and 'asp-post-hanlder' tag helper. But how can I call one of these methods from an AJAX call.

I have an older example with a typical MVC view and controller but how does this work with a Razor Page?

For example if I take the base application and modify the About.cshtml page to the following:

@page @model AboutModel @{ ViewData["Title"] = "About"; } <h2>@ViewData["Title"]</h2> <h3>@Model.Message</h3> <input type="button" value="Ajax test" class="btn btn-default" onclick="ajaxTest();" /> @section Scripts { <script type="text/javascript"> function ajaxTest() { console.log("Entered method"); $.ajax({ type: "POST", url: '/About', // <-- Where should this point? contentType: "application/json; charset=utf-8", dataType: "json", error: function (xhr, status, errorThrown) { var err = "Status: " + status + " " + errorThrown; console.log(err); } }).done(function (data) { console.log(data.result); }) } </script> } 

And on the model page About.cshtml.cs

public class AboutModel : PageModel { public string Message { get; set; } public void OnGet() { Message = "Your application description page."; } public IActionResult OnPost() { //throw new Exception("stop"); return new JsonResult(""); } } 

The OnPost is not called from the Ajax call.

3
  • What do you want to do? Can you show your view?
    – nbokmans
    CommentedSep 25, 2017 at 17:18
  • 1
    There isn't a 'View' just a Razor Page. I've expanded the example.CommentedSep 25, 2017 at 18:49
  • learn.microsoft.com/en-us/aspnet/core/mvc/razor-pages/… @page makes the file into an MVC action - which means that it handles requests directly, without going through a controller. ... The associations of URL paths to pages are determined by the page's location in the file system. The following table shows a Razor Page path and the matching URL: File name and path matching URL /Pages/Index.cshtml / or /Index /Pages/Contact.cshtml /Contact /Pages/Store/Contact.cshtml /Store/Contact /Pages/Store/Index.cshtml /Store or /Store/Index
    – Greg
    CommentedSep 25, 2017 at 19:42

8 Answers 8

20

Razor Pages automatically generates and validates Antiforgery tokens to prevent CSRF attacks. Since you aren't sending any token within your AJAX callback, the request fails.

To solve this problem you will have to:

  1. Register the Antiforgery-Service
  2. Add the token to your request
  3. Add the antiforgery token to your page either by adding a <form> or by directly using the @Html.AntiForgeryToken HtmlHelper

1. Register the Antiforgery-Service in your Startup.cs

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

2. Modify your AJAX callback

In the AJAX callback we add additional code to send the XSRF-TOKEN with our request header.

$.ajax({ type: "POST", url: '/?handler=YOUR_CUSTOM_HANDLER', // Replace YOUR_CUSTOM_HANDLER with your handler. contentType: "application/json; charset=utf-8", beforeSend: function (xhr) { xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val()); }, dataType: "json" }).done(function (data) { console.log(data.result); }) 

3. Add the antiforgery token to your page

You can accomplish this by adding a <form>:

<form method="post"> <input type="button" value="Ajax test" class="btn btn-default" onclick="ajaxTest();" /> </form> 

or by using the @Html.AntiForgeryToken:

@Html.AntiForgeryToken() <input type="button" value="Ajax test" class="btn btn-default" onclick="ajaxTest();" /> 

In both cases Razor Pages will automatically add a hidden input field which contains the antiforgery token once the page is loaded:

<input name="__RequestVerificationToken" type="hidden" value="THE_TOKEN_VALUE" /> 
6
  • 1
    I was having the same issue. If you look at your console output it will tell you that you are missing the XSRF-TOKEN.
    – causita
    CommentedSep 26, 2017 at 1:51
  • I'm still getting a Bad Request even after making those changes. This doc learn.microsoft.com/en-us/aspnet/core/security/… talks about making similar changes but that doesn't seem to work either :(CommentedSep 26, 2017 at 13:41
  • did you rebuild? I tested exactly the way I wrote it and I started getting responses back. Also make sure program.cs has using Microsoft.Extensions.DependencyInjection;
    – causita
    CommentedSep 26, 2017 at 13:42
  • Just created a new web app and modified the About page per my OP (also added an error handler see above) and your suggested changes. Console shows the following: Status: error Bad Request. This seems like the right path but I'm just missing something.CommentedSep 26, 2017 at 14:12
  • 3
    There is no need to add [ValidateAntiForgeryToken] attribute as Razor pages are designed to validate the Antiforgery token automatically.
    – VirendraJ
    CommentedOct 31, 2017 at 6:43
1

Please see this related section of the documentation https://learn.microsoft.com/en-us/aspnet/core/mvc/razor-pages/?tabs=visual-studio

The associations of URL paths to pages are determined by the page's location in the file system. The following table shows a Razor Page path and the matching URL

/Pages/Index.cshtml maps to / or /Index

/Pages/Contact.cshtml maps to /Contact

3
  • I understand the paths to the pages but the AJAX call is not triggering the OnPost method. Do you have an example of a working ajax call?CommentedSep 25, 2017 at 20:04
  • Are you getting bad request or not found?
    – causita
    CommentedSep 26, 2017 at 0:45
  • @Greg regaring Url, it is best to use @Url.Page to let asp.net construct correct url and pass parameters.CommentedMay 31, 2018 at 9:30
1

Everything works well, but some changes have to be made:

1)Open Startup.cs:

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

2)Open HomeController.cs:

[ValidateAntiForgeryToken] public IActionResult OnPost() { return new JsonResult("Hello Response Back"); } 

3)Open About.cshtml:

@{ ViewData["Title"] = "About"; } <h2>@ViewData["Title"]</h2> <h3>@ViewData["Message"]</h3> <p>Use this area to provide additional information.</p> <form method="post"> <input type="button" value="Ajax test" class="btn btn-default" onclick="ajaxTest();" /> </form> <script src="~/lib/jquery/dist/jquery.js"></script> <script type="text/javascript"> function ajaxTest() { $.ajax({ type: "POST", url: 'onPost', contentType: "application/json; charset=utf-8", beforeSend: function (xhr) { xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val()); }, dataType: "json" }).done(function (data) { console.log(data.result); }) } </script> 

It should be noted that "onPost" was added inside the controller, so in AJAX the correct "url" should be indicated. Then:

url: 'onPost', 
5
  • Just tested this out compared to the accepted answer and it does not seem to work. Did you test with the new Razor Pages and not MVC? The url should point to the page,CommentedNov 28, 2017 at 19:39
  • I created a new project "Asp.net Core 2.0" through VS2017 and I modified the View About. I have not created a new Controller, the url points to the folder containing the Home view. Once the application has been started, via the Debug of your Browser (F12 key), Network -> you can see the correct answer in "onPost".
    – piedatt80
    CommentedDec 1, 2017 at 20:12
  • You are creating a MVC web application. Not a Razor Page web app. About should be in under the Pages folder not View. I'm sure what you are doing is fine for MVC but it does not work with Razor Pages.CommentedDec 1, 2017 at 21:48
  • You are right. In fact I'm creating an application in "Asp.net Core 2" type Web Application MVC and it works.
    – piedatt80
    CommentedDec 2, 2017 at 14:55
1

After looking at the answers above I got JSON ajax to work with .NET Core 2.1 Razor pages using Visual Studio 2017 Preview 2:

Startup.cs

services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN"); 

PostJson.cshtml

@page @model myProj.Pages.PostJsonModel @{ ViewData["Title"] = "PostJson"; } <input type="button" value="Post Json" class="btn btn-default" onclick="postJson();" /> <script> function ajaxRazorPostJson(o) { return $.ajax({ type: "POST", data: JSON.stringify(o), url: 'postJson', contentType: "application/json; charset=utf-8", beforeSend: function (xhr) { xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val()); }, dataType: "json" }); } function postJson() { ajaxRazorPostJson({ reqKey: "reqVal" }).done(data => alert(data)); } </script> 

PostJson.cshtml.cs

using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Newtonsoft.Json.Linq; namespace myProj.Pages { public class PostJsonModel : PageModel { public IActionResult OnPost([FromBody] JObject jobject) { // request buffer in jobject return new ContentResult { Content = "{ \"resKey\": \"resVal\" }", ContentType = "application/json" }; // or ie return new JsonResult(obj); } } } 

Browser

http://localhost:44322/postJson

    0

    Accepted solution worked in local developing machine, but failed then deployed in Debian server behind Nginx reverse proxy (not found 404 error).

    This is a working example with payload data:

    <script type="text/javascript"> $('#btnPost').on('click', function () { var payloadData; /*asign payload data here */ $.post({ /* method name in code behind, and full path to my view*/ url: '@Url.Action("OnPostAsync", "/Turtas/Inventorius/InventoriausValdymas")', beforeSend: function (xhr) { xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val()); }, data: JSON.stringify({ payloadData }), contentType: "application/json; charset=utf-8", dataType: "json" }) }) </script> 

    VS 2017; .Net Core 2.2 Razor Pages; jQuery 3.3.1

      0

      I'm adding this for posterity. Wrangling the same problem, I discovered the following code which may be added to Startup.cs:

      services.AddRazorPages().AddRazorPagesOptions(options => { options.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute()); }); 

      No more anti forgery tokens required.

        -1

        The answer works for me. I would only add that if we have customized methods on the page like:

         public IActionResult OnPostFilter1() { return new JsonResult("Hello Response Back"); } 

        Then we should specify the handler name in the url:

        url: 'OnPost?handler=filter1', 
          -1

          The following works with ASP.NET Core MVC 3.1 using the headers setting:

          $.ajax({ type: "POST", url: '/Controller/Action', data: { id: 'value' }, headers: { RequestVerificationToken: $('input:hidden[name="__RequestVerificationToken"]').val() }, error: function (xhr, status, errorThrown) { var err = "Error: " + status + " " + errorThrown; console.log(err); } }).done(function (data) { console.log(data.result); }); 

          Including the ValidateAntiForgeryToken attribute on the controller method:

           [HttpPost] [ValidateAntiForgeryToken] public async Task<JsonResult> Action(string id) { var result = $"You sent '{id}'"; return Json(new { id, result }); } 

            Start asking to get answers

            Find the answer to your question by asking.

            Ask question

            Explore related questions

            See similar questions with these tags.