1

Not for the first time, I'm seeing that my Razor Pages seem to be handling GET/POST actions strangely when posting with ajax.

The latest example looks like this:

@inject IAntiforgery antiForgery
@{
    ViewData["Title"] = "Disclaimers";
    Layout = "~/Pages/Shared/_Blade.cshtml";

    var token = antiForgery.GetAndStoreTokens(HttpContext).RequestToken;
}

$(".save-button").on("click", function(e) {
    e.preventDefault;

    const body = document.querySelector(".editor").innerText;
    let disclaimer = {
        clientid: parseInt($("#Disclaimer_Client_Id").val()),
        description: $("#Disclaimer_Description").val(),
        type: $("#Disclaimer_Type").val(),
        markup: body
    };
    $.ajax({
        method: "GET",
        url: "./Create?handler=Create",
        headers: {
            "RequestValidationToken": "@token"
        },
        data: disclaimer,
        dataType: "application/json",
        success: function (data) {
            console.log(data);
        },
        error: function (data) {
            console.log(data);
        }
    });
});

I've done it this way because I'm using quilljs which employs a div for it's rich text editor. I can't use asp-for bindings on the div to bind it to the model.

public async Task<IActionResult> OnGetCreate(CreateDisclaimerViewmodel model)
{
    var disclaimer = new Disclaimer
    {
        Created = DateTime.Now,
        CreatedBy = User.Identity.Name,
        Description = model.Description,
        Markup = model.Markup,
        Type = model.Type
    };
    if (model.ClientId > 0)
    {
        disclaimer.Client = await context.Clients.FindAsync(model.ClientId);
    }

    context.Disclaimers.Add(disclaimer);
    await context.SaveChangesAsync();

    return Redirect("/Disclaimers/Index");
}

With the code set up as using a GET method, it all works, but in this case, it should clearly be a POST.

Change it to a POST however and an empty response is returned with HTTP 400...

$(".save-button").on("click", function(e) {
    e.preventDefault;

    const body = document.querySelector(".editor").innerText;
    let disclaimer = {
        clientid: parseInt($("#Disclaimer_Client_Id").val()),
        description: $("#Disclaimer_Description").val(),
        type: $("#Disclaimer_Type").val(),
        markup: body
    };
    $.ajax({
        // Only the method changes here, everything else is above.
        method: "POST",
        url: "./Create?handler=Create",
        headers: {
            "RequestValidationToken": "@token"
        },
        data: disclaimer,
        dataType: "application/json",
        success: function (data) {
            console.log(data);
        },
        error: function (data) {
            console.log(data);
        }
    });
});

And the page model:

// Only the method changes (OnGetCreate becomes OnPostCreate).
public async Task<IActionResult> OnPostCreate(CreateDisclaimerViewmodel model)
{
    var disclaimer = new Disclaimer
    {
        Created = DateTime.Now,
        CreatedBy = User.Identity.Name,
        Description = model.Description,
        Markup = model.Markup,
        Type = model.Type
    };
    if (model.ClientId > 0)
    {
        disclaimer.Client = await context.Clients.FindAsync(model.ClientId);
    }

    context.Disclaimers.Add(disclaimer);
    await context.SaveChangesAsync();

    return Redirect("/Disclaimers/Index");
}

This clearly should be a POST request but it simply won't work when using POST.

So what am I missing or misunderstanding? And since the solution can't be to use GET, what's the solution?

psiodrake
  • 316
  • 1
  • 9

1 Answers1

1

Shorten answer:

You use the wrong header name, it should be RequestVerificationToken:

headers: {
     "RequestVerificationToken": "@token"       
},

You are getting a 400 (Bad Request) response because the framework expects the RequestVerificationToken as part of the posted request. Be sure you have send it correctly.

Common way use ajax post in Razor Pages

Be sure your form tag does not have action attribute and then it will dynamically add a hidden input for token (You can F12 in browser to check whether your html contains input named __RequestVerificationToken, Ctrl+F and search for __RequestVerificationToken):

<form method="post">
     //other elements...  
    <input class="save-button" type="button" value="CHANGE"/>
</form>

@section Scripts
{
    <script>
$(".save-button").on("click", function(e) {
    e.preventDefault;

    //...
    $.ajax({
        // Only the method changes here, everything else is above.
        method: "POST",
        url: "?handler=Location",
        headers: {
              RequestVerificationToken: $('input:hidden[name="__RequestVerificationToken"]').val()              
        },
        data: disclaimer,  
        dataType: "application/json",
        //...
    });
});
</script>
}

Otherwise, you will need manually add it by using @Html.AntiForgeryToken() in the form:

<form method="post">
     //other elements... 

      @Html.AntiForgeryToken() 
    <input class="save-button" type="button" value="CHANGE"/>
</form>
Rena
  • 30,832
  • 6
  • 37
  • 72
  • using `IAntiForgery`, I set `var token = antiForgery.GetAndStoreTokens(HttpContext).RequestToken;` which is what I'm putting in the javascript. I updated the question with the first code block including the code to get the token – psiodrake Apr 04 '22 at 10:14
  • Hi @psiodrake, If you are sure the token is not null, your mistake here is use the wrong header name, it should be `RequestVerificationToken`. – Rena Apr 04 '22 at 10:47