Brad Patton
Brad Patton

Reputation: 4205

Example AJAX call back to an ASP.NET Core Razor Page

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.

Upvotes: 8

Views: 49194

Answers (8)

sanepete
sanepete

Reputation: 1120

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.

Upvotes: 0

Doug Dekker
Doug Dekker

Reputation: 353

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 });
    }

Upvotes: -1

causita
causita

Reputation: 1708

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" />

Upvotes: 20

Exc1t4r
Exc1t4r

Reputation: 1

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

Upvotes: 0

kofifus
kofifus

Reputation: 19276

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

Upvotes: 1

Nije Vazno
Nije Vazno

Reputation: 11

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',

Upvotes: -1

piedatt80
piedatt80

Reputation: 27

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',

Upvotes: 1

Greg
Greg

Reputation: 2600

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

Upvotes: 1

Related Questions