ASP.NET Core 2, button click with Razor pages without MVC

Mark picture Mark · Nov 23, 2017 · Viewed 30.2k times · Source

As someone pointed out in a comment, Razor pages doesn't need Controllers, like you're used to do in MVC. I also now Razor doesn't have a native handling of the button click event. To do something (in the "code-behind") when the user click a button we have at least two options:

  • use a submit button
  • use an ajax call

I find a LOT of examples for MVC that show how to define the Controller functions. But as said, I don't have one.

Hence I'm trying to understand both ways. Here my test cshtml:

<form method="post">
    <input type="submit" class="btn btn-primary" value="way1">Way 1/>
</form>

<button id="btn" type="button" class="btn btn-primary" value="way2">Way 2</button>

<script>
$(document).ready(function() {
    $("#btn").click(function (e) {
        e.preventDefault();
        $.ajax({
            url: "@Url.Action("Way2")",
            type: "POST",
            data: "foo",
            datatype: "text",
            success: function (data) {
                alert(data);
            }
        });
        return false;
    });
});
</script>

and here the cshtml.cs:

public class TestModel : PageModel { private readonly MyContext _context;

public TestModel(MyContext context)
{
    _context = context;
}

public IActionResult OnPost()
{
    // here I can put something to execute 
    // when the submit button is clicked
}

public ActionResult Way2(string data)
{
    // this is never triggered
}

Questions

  1. With the "Way1" (submit) I'm able to catch the click of a button, but with some drawbacks. By default the page is reloaded due to the post. Sometimes I don't need to change anything, but just call a C# function. But I don't understand how to handle multiple buttons. I.e. if I have 5 buttons, how to do something different for each one?

  2. With the "Way2" I did something wrong because the function in cs code is never reached and I get a Bad Request error (400). What did I miss?

Answer

Shyju picture Shyju · Nov 23, 2017

Normal form submit

When doing a normal form submit, As per the convention, you need the handler method name to follow On{{HttpVerb}}{{YourHanderName}} format

public ActionResult OnPostWay2(string data)
{
    // to do : return something
}

Now make sure you have your submit button inside a form tag and you mentioned the asp-page-handler. The value of that attribute should be your handler name in the page model class (Way2)

<form method="POST">       
    <button type="submit" asp-route-data="foo" asp-page-handler="Way2">Way 2</button>
</form>

The above code will generate the markup for button with a formaction attribute which is set to the url yourBaseUrl/YourPageName?data=foo&handler=Way2. When the use clicks on the submit button, it will post the form this url because formaction attribute value will override the default action url of the form. When the request is received, the razor pages framework will use this parameter (handler) and direct the request to the corresponding handler method.

Ajax call

You are getting a 400 (Bad Request) response because the framework expects the RequestVerificationToken as part of the posted request data. If you check the view source of the page, you can see a hidden input element with name __RequestVerificationToken inside the form. The framework uses this to prevent possible CSRF attacks. If your request does not have this information, the framework will return the 400 bad request.

To make your ajax code works, all you have to do is, send this explicitly. Here is a working sample

$("#btn").click(function(e) {
    e.preventDefault();

    var t = $("input[name='__RequestVerificationToken']").val();
    $.ajax({
        url: $(this).attr("formaction"),
        headers:
        {
            "RequestVerificationToken": t
        },
        type: "POST",
        data: { data: 'foo2' },
    }).done(function(data) {
        console.log(data);
    }).fail(function(a, v, e) {
        alert(e);
    });
});

Now since you are making an ajax call, it makes sense to return a json response

public ActionResult OnPostWay2(string data)
{
    return new JsonResult("Received "+ data + " at "+DateTime.Now);        
}

In the above example, we are using some jQuery code to get the input element with name __RequestVerificationToken and reading the value of it. A more robust approach would be injecting the IAntiforgery implementation to the view and using the GetAndStoreTokens method.

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
    public string GetAntiXsrfRequestToken()
    {
        return Xsrf.GetAndStoreTokens(Model.HttpContext).RequestToken;
    }
}
<script>
     $(function () {

         $("#btn").click(function(e) {
             e.preventDefault();
             var t = '@GetAntiXsrfRequestToken()';
             $.ajax({
                      url: $(this).attr("formaction"),
                      headers:
                      {
                          "RequestVerificationToken": t
                      },
                      type: "POST",
                      data: { data: 'foo2' },
             }).done(function(data) {
                     console.log(data);
             }).fail(function(a, v, e) {
                     alert(e);
             });
         });
    })
</script>