asp-for tag adds required field validation on checkbox in asp.net core - asp.net-core

I have asp.net core application and im trying to add simple checkbox without any validation. Checkbox is bound to boolean property on model. Below is the code
Model
public class MyModel
{
public bool IsEmployee { get; set; }
}
cshtml
<form>
<div>
<label asp-for="IsEmployee">Is Employee</label>
<input type="checkbox" asp-for="IsEmployee"/>
</div>
<button id="btnSave" class="btn btn-primary" type="button">Save</button>
</form>
<script src="~/js/test.js"></script>
javascript
$(function () {
var kendoValidator = $('form').kendoValidator().data("kendoValidator");
$('#btnSave').click(function () {
if (kendoValidator.validate()) {
alert('true');
}
else {
alert('false');
}
})
})
I am using asp-for tag helper on input element. Note that IsEmployee property DOES NOT have [Required] attribute. But because of asp-for tag helper the rendered html has data-val-required and data-val attributes on input element. It also adds one more hiddden input element with same name.
Below is rendered html.
(also note that i think it only happens when input type is checkbox. for textboxes its working fine)
<form novalidate="novalidate" data-role="validator">
<div>
<label for="IsEmployee">Is Employee</label>
<input name="IsEmployee" id="IsEmployee" type="checkbox" value="true" data-val-required="The IsEmployee field is required." data-val="true">
</div>
<button class="btn btn-primary" id="btnSave" type="button">Save</button>
<input name="IsEmployee" type="hidden" value="false">
</form>
I am using kendovalidator as well which adds data-role="validator" on form element.
Issues
There are 2 issues here
1> As soon as i click on check the box error message appears as The IsEmployee field is required.
2>kendoValidator.validate() method always returns false regardless of checkbox is selected or not.
Demo here JSFiddle
Update 2
We cannot bind nullable bool to checkbox. I am using asp.net core. I am not sure what the equivalent syntax in asp.net core for the suggestion here which is valid for classic asp.net

Add data-validate="false" to the checkbox input. The kendoValidator will ignore all inputs with that attribute set to false.
<input type="checkbox" asp-for="IsEmployee" data-validate="false" />

If you don't wan't the default generated html you have 2 choices.
Don't use it ! You are not forced to use the tag helpers, they are there for when you do need other html attributes generated. In this case just use < input name="IsEmployee" ...>
Change the way asp-for behaves for your checkbox. You can do this be either creating your own IHtmlGenerater or by extending the DefaultHtmlGenerator and overriding GenerateCheckBox and possibly GenerateInput and then registering it with something like services.TryAddSingleton();
Hope this helpes you.

Related

Post form with a page handler after changing the value in Select input

I am rather new to .NET 6/Razor pages. I want to mimic the behavior of the web forms when the drop down list value was changed. In web form, I could do OnSelectedIndexChanged and it would hit a specific method in the code behind. What is the best way to do this with a razor page?
Currently, I have
<button class="btn btn-info text-white" asp-page-handler="ResetForm"><i class="fa-solid fa-ban"></i> Clear</button>
<select asp-for="CurrentPage" onchange="ddlCurrentPageChange()" asp-items="Model.DdlPages" ></select>
<script>
function ddlCurrentPageChange() {
document.getElementById("form");
}
</script>
The issue is if I click the reset button and then change the DDL value, it posts to the handler ResetForm
You could always make your onchange the submit action, which will POST:
<form method="post" id="test">
<select asp-for=CurrentPage onchange="document.forms['test'].submit();" asp-items=#Model.ddlPages ></select>
</form>
page.cshtml.cs
public void OnPost(string currentPage)
{
MoveTo(currentPage);
}
public void MoveTo(string page)
{
Console.WriteLine(page);
}
EDIT: Fixed incorrect data as pointed out by #jeremycaney

Blazor problem connecting the UI with the code

I'm using Blazor Server application in Visual Studio 2019. In the .razor page I have:
<div class="container">
<div class="row">
<div class="col-md">
<label for="ConnectionStringEdit" id="Label1">Connection String for destination</label>
</div>
<div class="col-md-7">
<input type="text" id="ConnectionStringEdit" name="ConnectionStringEdit" text=#ConnectDestination spellcheck="false" style="width: 585px; height: 26px;" class="form-control">
</div>
<div class="col-md-auto">
<input type="submit" id="btnConnect" name="btnConnect" value="Connect" class="btn btn-primary" #onclick="Connect1">
</div>
</div>
</div>
now in the code part I have
#code {
private string ConnectDestination { get; set; } = "";
private void Connect1()
{
if (ConnectDestination.Length > 0)
{
// do something
}
}
}
When I insert something in the Input and I press the button, ConnectDestination doesn't take the value of the Input Control. So this last If condition is never true. How do I get the inserted value of the Input control named ConnectionStringEdit?
Thanks
It should be #bind-value="#ConnectDestination"
you could also use the short directive #bind instead:
#bind="#ConnectDestination"
Note: All the input element's types are bound through the value attribute of the element.
Note: Both #bind-value and #bind are compiler directive instructing the compiler to emit code, behind the scene, that enables two way data-binding between a variable and an Html tag. The compiler create a two-way data binding by binding a variable to the value attribute of the element, something equivalent to this:
value="#ConnectDestination", which creates a one direction binding from the variable to the bound element. The compiler also creates an event call back which enables binding from the element to the variable, something equivalent to this:
#onchange="#((args) => ConnectDestination = args.Value?.ToString())"
This means that you could do that yourself, if you wish to have more control over the binding. You'll usually do something like this:
value="#ConnectDestination" #onchange="OnChange"
And define the call back method like this:
private void OnChange(ChangeEventArgs args)
{
// Note that it is your responsibility to update the
// ConnectDestination variable:
ConnectDestination = args.Value?.ToString());
}
Note: This is wrong:
<input type="submit" id="btnConnect" name="btnConnect" value="Connect" class="btn btn-primary" #onclick="Connect1">
The type attribute of the input element should be set to button:
<input type="button"
Blazor App is an SPA... meaning no submit. The only place you use the "submit" button is when you use the EditForm component, and even then the "submit" action is intercepted and canceled by the Blazor.
You can try
<input type="text" id="ConnectionStringEdit" name="ConnectionStringEdit" #bind=#ConnectDestination spellcheck="false" style="width: 585px; height: 26px;" class="form-control">
or
<input type="text" id="ConnectionStringEdit" name="ConnectionStringEdit" value="#ConnectDestination"
#onchange="#((ChangeEventArgs __e) => ConnectDestination = __e?.Value?.ToString())" spellcheck="false" style="width: 585px; height: 26px;" class="form-control">

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.

Razor tag helper bool Radio buttons clientside 'Required' validation

I am using Razor Pages Tag helpers for a Yes/No in a form on a page. All other fields in the form have client side unobtrusive validation, but the bool? Yes/No does not. I have a few radio Yes/No's like this, how do I get the client side to work for them?
[Required]
public bool? Have7DaysWorth { get; set; }
I tried moving away from tag helpers too, butit doesn't hook up with this:
<label asp-for="Have7DaysWorth" class="control-label"></label>
<div>
<input type="radio" id="daysRadio1" name="Have7DaysWorth "
checked="#(Have7DaysWorth == true ? "checked": null)"
class="custom-control-input" value="true">
<label class="custom-control-label" for="daysRadio1">Yes</label>
</div>
<div>
<input type="radio" id="pharmacyRadio2" name="Have7DaysWorth "
checked="#(Have7DaysWorth == false ? "checked": null)"
class="custom-control-input" value="false">
<label class="custom-control-label" for="daysRadio1">No</label>
</div>
<span asp-validation-for="Have7DaysWorth " class="text-danger"></span>
I know from searching there are some suggestions about pre-selecting one, but that isn't then a conscious value that a user has entered into a form, so not an option here.
I have tried some other ways, but they seemed to lose the value when the modelstate wasn't valid and was returned.
How do I get the client side to work for bool radios in the expected way that I want?
You're not using asp-for on the radios, so it has no idea it should be required, as there's no involvement with the model, and thus the Required attribute on that property.
An old thread but if you're having to upgrade a razor page website to Bootstrap 5 you'll run into this issue.
This will not work as you cannot use asp-for with C# statement not inside an attribute
<input asp-for="MarketingPrefs" type="radio" class="btn-check" value="true" #(Model.MarketingPrefs.HasValue && Model.MarketingPrefs.Value ? "checked" : null)>
If you omit the asp-for like this then local validation won't work
<input id="MarketingPrefsYes" name="MarketingPrefs" type="radio" class="btn-check" value="true" #(Model.MarketingPrefs.HasValue && Model.MarketingPrefs.Value ? "checked" : null)>
This is how to do it
<div class="btn-group" role="group">
#Html.RadioButtonFor(model => model.MarketingPrefs, "true", new { #class = "btn-check", #id="MarketingPrefsYes" })
<label class="btn btn-outline-secondary" for="MarketingPrefsYes">Yes</label>
#Html.RadioButtonFor(model => model.MarketingPrefs, "false", new { #class = "btn-check", #id="MarketingPrefsNo" })
<label class="btn btn-outline-secondary" for="MarketingPrefsNo">No</label>
</div>

Checkbox Click Form Submit in Razor Pages AspNetCore 2.2

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.