Checkbox Click Form Submit in Razor Pages AspNetCore 2.2 - asp.net-core

I am trying to (non-Ajax) get a checkbox to resubmit form in Razor Pages and reload the page / and catch the result in my OnPost method.
I have in my index.cshtml
#page "{id:int?}"
#model IndexModel
<div class="text-center">
<form action="post" name="form1">
<strong>Filter:</strong> Hide Single Runs <input onclick="document.form1.submit()" asp-for="#Model.HideSingle" />
<hr />
And in my PageModel
public class IndexModel : PageModel
{
public bool HideSingle { get; set; } = true;
public async Task OnPost(int? id, bool hideSingle)
{
for say a starting page URL:
http://localhost:5000/TestRuns/1
The form submits on the checkbox click, but it ends up with a Url:
http://localhost:5000/TestRuns/post?HideSingle=false
Which obviously fails to resolve as I am expecting a route of http://localhost:5000/TestRuns/1.

For Asp.net Core form, the default method is Get which means for your current code, it send request with Get instead of post. You could specify the method with post like
<div class="text-center">
<form method="post" name="form1">
<strong>Filter:</strong> Hide Single Runs <input onclick="document.form1.submit()" asp-for="#Model.HideSingle" />
<hr />
</form>
</div>
For another way, you could explictly set the handler like
<div class="text-center">
<form asp-page-handler="post" name="form2">
<strong>Filter:</strong> Hide Single Runs <input onclick="document.form2.submit()" asp-for="#Model.HideSingle" />
<hr />
</form>
</div>

<input onclick="document.form1.submit()" />
is using JavaScript to submit the form. Do you want the page to post back, since you're not using Ajax (from your question)?
You want the call from your page to end up being http://localhost:5000/TestRuns/post?id=1&HideSingle=false. Is id required for your post to work (it's unclear with OnPost(int? id..)?
I order to get both values, you'll need to have hidden form values or multiple <input asp-for="Email" class="form-control" />. You need 1 for hideSingle and on for the id.

Related

Is anti forgery token added automatically, even without explicit [AutoValidateAntiforgeryToken]?

Context
I've noticed that after creating a new ASP.NET Core Razor page application in VS 2019 from its out of the box template, even the purest html form with the purest model class renders output with <input name="__RequestVerificationToken" type="hidden" value="...">
Question
Am I missing something and there is somewhere an explicit attribute/statement which instructs ASP.NET Core to add anti forgery or now this is the default? (which makes using [AutoValidateAntiforgeryToken] obsolete)
...or...
It is just the <input name="__RequestVerificationToken" type="hidden" value="..."> which is rendered always unconditionally and with the [AutoValidateAntiforgeryToken]I can turn on the server side validation against it? This case how can I smoke test if validation is in effect or not?
Sample Code
#page
#model TestFormModel
#{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<form method="post">
<input type="text" name="myinput"/>
<input type="submit" value="Submit" />
</form>
</div>
//[AutoValidateAntiforgeryToken]
public class TestFormModel : PageModel
{
private readonly ILogger<TestFormModel> _logger;
public TestFormModel(ILogger<TestFormModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
public void OnPost()
{
}
}
Previously in .NET Framework versions of ASP.NET you did have to opt-in to anti-forgery token usually with an attribute.
[ValidateAntiForgeryToken]
public ActionResult Save(Product product)
{
db.Product.Add(product);
Return View();
}
In ASPNET Core this automagically included in the Form Tag Helper. So any time your CSHTML includes a FORM element, the hidden field is included for you by the ASPNET Core runtime.
The basis for including this by default is the mantra of "Convention over configuration". By convention, 80+% of developers would opt to protect their application against CSRF attacks. If you wish to go against the convention, you can find the option to opt out in the conventions helper in the ConfigureServices portion of your Startup class.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddRazorPagesOptions(options =>
{
options.Conventions
.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
});
}
This blog post goes in further detail specific to Razor Pages, options and usage scenarios.
Update - Response to comment
If you read the a code, you may notice that there is no taghelper. –
g.pickardou
There is indeed a tag helper. In a new Razor Pages project template you can find the tag helpers are included in the _ViewImports.cshtml file here:
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
We can validate that your <form /> element, as written in the OP is invoking an ASP.NET tag helper as follows:
<form method="post">
<input type="text" name="myinput"/>
<input type="submit" value="Submit" />
</form>
If we inspect the page source on this, you will see the result
<form method="post">
<input type="text" name="myinput" />
<input type="submit" value="Submit" />
<input name="__RequestVerificationToken" type="hidden" value="{{token}}" />
</form>
Now, if we use the syntax to opt out of individual tag helpers
<!form method="post">
<input type="text" name="myinput" />
<input type="submit" value="Submit" />
</!form>
And again inspect the page source we can clearly see we have explicitly opted out of this tag helper.
<form method="post">
<input type="text" name="myinput" />
<input type="submit" value="Submit" />
</form>
For .Net 3.1 the form helper does add the validation token to forms when you use it like <form asp-action="...
With asp.net core 3.1 with a form that does not use asp-action and or asp-controller
like:
<form asp-action="Index" asp-controller="Home" method="post">
and uses this:
<form action="Index" method="post">
To include this: (in the form {before the closing form: })
<input name="__RequestVerificationToken" type="hidden" value="..." />
I just add this to the form:
asp-antiforgery="true"
like:
<form action="Index" method="post" asp-antiforgery="true">
Always works for me
This does not work for me:
<input name="__RequestVerificationToken" type="hidden" value="{{token}}" />
I just get that exact thing which doesn't have the token.
Of course you then need the decorator before your method like:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Update(...
Hope that helps someone searching for how to include the RequestVerificationToken or ValidateAntiForgeryToken
For the later Core versions (6,7 and maybe earlier), here's what the documentation states regarding when/if a token will be generated automatically:
The automatic generation of antiforgery tokens for HTML form elements happens when the <form> tag contains the method="post" attribute and either of the following are true:
The action attribute is empty (action="").
The action attribute isn't supplied (<form method="post">).
That mean that if to set a form's action attribute to a custom value, the "Antiforgery" element won't be injected automatically.
Here are a few ways one can do in those cases to have one injected:
Add the tag helper asp-antiforgery="true" to the form element
Add #Html.AntiForgeryToken() within the form element
Add #inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf to the view and
<input id="__RequestVerificationToken" type="hidden" value="#Xsrf.GetAndStoreTokens(Context).RequestToken" /> within the form element
Use the submit element's formaction attribute instead of the form's.

ASP.NET CORE Razor Pages: How to avoid executing page handler 2nd time on page refresh?

When using method handlers to execute OnGet or OnPost methods, &handler=[action] query string gets added.
Problem is if user manually refreshes the page afterwards by hitting browser's refresh button, the same action will get executed for the 2nd time unintentionally.
What is the recommended approach to avoid this?
Problem is if user manually refreshes the page afterwards, same action
will get executed for the 2nd time.
For the browser refresh button click event, we can't prevent it. But, as a workaround, you could defined a TriggerCount property in the page model, and use a hidden field to store the value in the form, then in the handler method, get the hidden field value and based on the count to do something. Code as below:
code in the .cshtml.cs page:
public void OnPostDelete()
{
if (Request.Form["TriggerCount"].Count > 0)
{
TriggerCount = Convert.ToInt32(Request.Form["TriggerCount"]);
TriggerCount++;
}
if (TriggerCount < 2)
{
// do something.
Message = "Delete handler fired, Count:" + TriggerCount;
}
else
{
Message = "Over 2 times";
}
}
Code in the .cshtml page:
#page
#model RazorPageSample.Pages.HandlerPageModel
#{
}
<div class="row">
<div class="col-lg-1">
<form asp-page-handler="edit" method="post">
<button class="btn btn-default">Edit</button>
</form>
</div>
<div class="col-lg-1">
<form asp-page-handler="delete" method="post">
<input type="hidden" asp-for="TriggerCount" />
<button id="btndelete" disabled="#(Model.TriggerCount>=1?true:false)" class="btn btn-default">
Delete
</button>
</form>
</div>
</div>
<h3 class="clearfix">#Model.Message</h3>
the screenshot as below:

razor pages form post handling

I have a simple html form
<form method="post" asp-page="testbank" enctype="application/x-www-form-urlencoded">
<input type="text" name="d" />
<button type="submit">Submit</button>
</form>
and I want to post to another razor page called testbank. In the testbank razor page I have
public void OnPost()
{
var a = Request.Form["d"];
}
but the post action is not triggered even when I add a breakpoint. I can't figure out why is that happening. I want to post data to another razor page.

Why is asp-route passing on post value "0"

In my razor page in form I have button that on submit calls method "OnPostEditMe" with parameter "int id":
#{
var idbook = #Html.DisplayFor(a => a.EditBook.Id);
//idbook on page load gets id number of object EditBook when page loads, this works, if I add this value anywhere in razor page it displays correctly
}
<form method="post">
<div class="form-group row">
<div class="col-3 offset-3">
<button asp-page-handler="EditMe" asp-route-id="#idbook" type="submit">
Update #idbook
</button>
</div>
</div>
</form>
But when I click on this button, It calls method OnPostEditMe, but with parameter "0" instead value that was load with "idbook":
public async Task<IActionResult> OnPostEditMe(int id)
{
var tmp = id; //id is always 0
...
}
What is wrong, Why does asp-route-id="#idbook" not passing parameter that was load on page?
You have to set on the form instead of the button
<form method="post" asp-route-id="#idbook">
Or you can use hidden input
<input value="#idbook" name="id" id="id" type="hidden"/>.

Razor Pages ASP.NET unobtrusive ajax Post partial update

I'm trying to update only part of my page from a partial View.
It works perfectly fine if i use this
Click heeeeeeeere
But this is a simple get and i'd like to actually post some data and do something with it. I wrote a form, set its method to post like this.
<form method="post" data-ajax="true" data-ajax-method="post" data-ajax-complete="completed" data-ajax-update="#panel" >
<div class="row">
id : #Html.TextBoxFor(x => x.customer.ID)
</div>
<div class="row">
Name : #Html.TextBoxFor(x => x.customer.Name)
</div>
<div class="row">
<input type="submit" value="send data" />
</div>
</form>
BUT this updates my entire page so my entire page is just the little partial view thats supposed to be updated.
a first observation, it seems you are missing the data-ajax-url from the second form .
Saying that, then in your Razor view you should include on the top of the page
#page "{handler?}"
This will allow you to pass additional information to your handler, then in your form you can simply include something like
<input type="hidden" name="id" value="send value"/>
where value is the value you want to pass and name is how the handler will identify what property to bind this to, then in your .cshtml.cs page your handler should look something like this
public IActionResult OnPostPartial(string id) {...//do something here id == "send value"}
hope this helps