I have a field in a form that I need to be required if a checkbox in the same form is checked.
There doesn't seem to be a way to do this with regular model validation, so I created a remote validation method in my controller.
The problem is, since the field isn't required always, the validation doesn't even fire if I put it on the field. So I tried putting the validation on the checkbox, and now I get a different problem where the validation doesn't fire when I add text to the field.
Is there a way to do what I'm needing with custom validation, or do I need to do something in JavaScript? If so, what do I need to do?
Form:
<form>
<input type="checkbox" asp-for="NotRecommended" checked=#Model.NotRecommended /> <label>Not Recommended</label>
<textarea class="form-control" id="Notes" asp-for="Notes"></textarea>
<span asp-validation-for="NotRecommended" class="text-danger"></span>
</form>
Model:
public class DetailsViewModel
{
[DisplayName("Not Recommended")]
[Remote("RequireNoteForFlag", AdditionalFields = "Notes", ErrorMessage = "Note is required when flagging someone.")]
public bool NotRecommended { get; set; }
[DataType(DataType.MultilineText)]
[MaxLength(1500)]
[DisplayName("Notes")]
public string Notes { get; set; }
}
Remote Validator
public IActionResult RequireNoteForFlag(bool NotRecommended, string Notes)
{
if (NotRecommended && String.IsNullOrWhiteSpace(Notes)) return Json("Note is required when flagging an Assessor");
else return Json(true);
}
Joe gave me pretty much the whole answer in the comments, but hasn't posted it as an answer, so I'll post it for anyone else who might need to do this.
Create the Attribute
https://github.com/joeaudette/cloudscribe/blob/master/src/cloudscribe.Web.Common/DataAnnotations/RequiredWhenAttribute.cs
Joe's Client-Side Validation
https://github.com/cloudscribe/cloudscribe/blob/master/src/cloudscribe.Web.StaticFiles/js/cloudscribe-validation-requiredwhen.js
Modified Client-Side Validation
jQuery.validator.addMethod("requiredwhen", function (value, element, param) {
var otherPropId = $(element).data('val-other');
if (otherPropId) {
var otherProp = $(otherPropId)[0].checked;
if (otherProp) {
return element.value.length > 0;
}
}
return true;
});
jQuery.validator.unobtrusive.adapters.addBool("requiredwhen");
View
<input type="checkbox" asp-for="NotRecommended" /> <label asp-for="NotRecommended"></label>
<textarea class="form-control" id="Notes" asp-for="Notes"></textarea>
<span asp-validation-for="NotRecommended" class="text-danger"></span>
Model
[DisplayName("Not Recommended")]
public bool NotRecommended { get; set; }
[DataType(DataType.MultilineText)]
[MaxLength(1500)]
[DisplayName("Notes")]
[RequiredWhen("NotRecommended", true, AllowEmptyStrings = false, ErrorMessage ="A note is required when flagging an Assessor.")]
public string Notes { get; set; }
I have implemented this with a custom validator in a similar way. However I applied the annotation to the field that would be required and included a condition in the arguments, eg:
[RequiredIf("NotRecommended = true")]
public string Notes { get; set; }
But, as you may already be aware, there is no way to cross-reference another property in data annotations so you will need to build in the javascript to handle this.
Unobtrusive validation adds a data-val="true" and data-val-required="error message" attribute to the input element so adding this in via javascript when the checkbox is checked will work.
You will also need to reinitialise the form validation so that the inputs with newly applied validation attributes are added.
Related
I want to take only selected checkboxes. I tried this.
<input class="form-check-input" type="checkbox" name="Box" value="LDL" />
<label for="Box">LDL</label><br>
<input class="form-check-input" type="checkbox" name="Box" value="LDL" />
<label for="Box">LDL</label><br>`enter code here`
<input class="form-check-input" type="checkbox" name="Box" value="HDL" />
<label for="Box">HDL</label><br>
<div class="button-holder d-flex justify-content-center">
<button type="submit" class="btn btn-success">Send</button>
</div>
This is the Action
[HttpPost]
public IActionResult SendTest(ListParams listParams)
{
}
This is the DTO
public class CheckboxParams
{
public string Box { get; set; }
public bool IsChecked { get; set; }
}
public class ListParams
{
public ListParams()
{
this.AllCheckedParams = new List<CheckboxParams>();
}
public List<CheckboxParams> AllCheckedParams { get; set; }
}
In asp.net core, Model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix. Besides, the nested array matches the [index].property_name or property_name[index].property_name.
In your code, the name of the checkbox could not match the property in ListParams model. The correct way should be AllCheckedParams[index].box.
But for your scenario, your model design is not correct. The browser will only send the selected checkbox to the backend by default when form submit. If you choose the first two or last two(anyway,that is to say the index should be consecutive) checkbox, it works well. If you choose the first and third checkbox, it does not work any more. Because the name of them is AllCheckedParams[0].box and AllCheckedParams[2].box. They are not consecutive index of the array, so only the first checkbox could be matched and passed to the backend.
The correct way is that just change your backend received data like below:
[HttpPost]
public IActionResult SendTest(string[] box)
{
}
If you still want to use the model, you need change the model like below:
public class ListParams
{
public List<string> Box { get; set; }
}
Controller:
[HttpPost]
public IActionResult SendTest(ListParams listParams)
{
}
The HtmlHelper class provides two extension methods to generate a element in an ASP.NET MVC view. They are as follows:
CheckBox()
CheckBoxFor()
for creating form in razor page you can use
#model Student
#Html.CheckBoxFor(m => m.isActive)
The Html.CheckBox() is a loosely typed method which generates a with the specified name, isChecked boolean, and HTML attributes.
#Html.CheckBox("isActive", true)
The App: It is fed an order #, and the part ID, part #, and quantity for each part are displayed (so 3 input fields per line, per part). All those values appear as input fields that can be modified. In the PageModel, I store all those values in Lists and then iterate through them to find ones that have been modified. If I find modifications, I update the appropriate parts of the database. The app is to be used 100% in-house by people who know what they are doing. Everything I mentioned above is working swimmingly. The functionality is there.
The relevant front-end is this:
#{ int index = 0;}
#foreach (var part in Model.PartInfoList)
{
<tr class="row table-bordered">
<td class="col-4">
<input type="hidden" asp-for="OldPartIDList" value="#part.PART_ID" name="OldPartIDList"/> #*This holds the "old" part ID's so the order can still be looked up when the user wants to change the part ID.*#
<input type="text" class="form-control mb-2 mr-sm-2" asp-for="NewPartIDList" value="#part.PART_ID" id="#part.PART_ID" name="NewPartIDList[#index]" onchange="HighlightField(this)" />
#*The id here is used to determine if the field should be highlighted or not.*#
<span class="text-danger" asp-validation-for="NewPartIDList[index]"></span>
</td>
<td class="col-4">
<input type="text" class="form-control mb-2 mr-sm-2" asp-for="PartNumberList" value="#part.PART_NUMBER" id="#part.PART_NUMBER" name="PartNumberList[#index]" onchange="HighlightField(this)" />
#*The id here is used to determine if the field should be highlighted or not.*#
<span class="text-danger" asp-validation-for="PartNumberList[index]"></span>
</td>
<td class="col-4">
<input type="text" class="form-control mb-2 mr-sm-2" asp-for="QuantityList" value="#part.QUANTITY" id="#part.QUANTITY" name="QuantityList[#index]" onchange="HighlightField(this)" />
#*The id here is used to determine if the field should be highlighted or not.*#
<span class="text-danger" asp-validation-for="QuantityList[index]"></span>
</td>
</tr>
index++;
}
The lists are declared as such:
[DisplayName("Part ID"), BindProperty, Required]
//[Range(1, 999999999, ErrorMessage = "Please enter a number between 1 and 999,999,999.")]
[ResubmitSampleCenterOrderValidatorClass(FieldOptions.PART_ID, 1, 999999999, null)]
public List<int> NewPartIDList { get; set; }
[DisplayName("Part Number"), BindProperty, Required]
//[StringLength(48, MinimumLength = 1, ErrorMessage = "Please enter a part number.")]
//[ResubmitSampleCenterOrderValidatorClass(FieldOptions.PART_NUMBER, 1, 999999999, PartNumberArray: PartNumberList.ToArray())]
public List<string> PartNumberList { get; set; }
[DisplayName("Quantity"), BindProperty, Required]
//[Range(1, 99999, ErrorMessage = "Please enter a number between 1 and 99,999.")]
[ResubmitSampleCenterOrderValidatorClass(FieldOptions.QUANTITY, 1, 99999, null)]
public List<int> QuantityList { get; set; }
The comments are what I've been trying. More on that later.
The Problem: Validation, on the part number input fields specifically. I cannot use
[StringLength(48, MinimumLength = 1, ErrorMessage = "Please enter a part number.")]
because the code will try to convert the string list to a string and that obviously can't happen. I just want to:
1) Make sure each part # cannot exceed 48 characters.
2) Have the error show up below that exact input field and NOT the others (This is actually an issue with the part ID and quantity fields as well, but I care more about the part # fields right now.)
Things I've tried:
As you can see from the comments above, I tried normal validation and it did not work for the above reasons. I then decided to go to the custom validation route:
public class ResubmitSampleCenterOrderValidatorClass: ValidationAttribute
{
public FieldOptions FieldDescriptor { get; set; }
public int MinLength { get; set; }
public int MaxLength { get; set; }
public List<string> PartNumberList { get; set; }
public string[] PartNumberArray { get; set; }
public ResubmitSampleCenterOrderValidatorClass(FieldOptions FieldDescriptor, int MinLength, int MaxLength, string[] PartNumberArray)
{
this.FieldDescriptor = FieldDescriptor;
this.MinLength = MinLength;
this.MaxLength = MaxLength;
this.PartNumberArray = PartNumberArray;
PartNumberList = new List<string>();
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
switch(FieldDescriptor)
{
case FieldOptions.PART_NUMBER:
break;
}
return null;
}
}
FieldOptions is just an enum, of which PART_NUMBER is 1. Anyways, I couldn't find a way to pass the string list into the custom validator because it isn't and cannot be static. I also couldn't find a way to get my numbers out of value for the part ID and quantity, but that's a separate issue.
I also put indexes on the name attributes for my input fields on the front end, hoping that could eventually lead to a solution. It was a trick I used in a JS/jQuery app I wrote a few years ago to do something very similar. Is there a way to access specific elements from the back-end, so I could just iterate through each one of them and validate them myself?
I have googled and googled, but I can't find anyone else with this issue. Maybe I haven't found the magic search words yet. I feel that if my part # validation woes are solved, I can use that solution on the problems I'm having with the other two fields.
All ideas are welcome! Let me know if you need more clarification! Thank you and have a fantastic day!
EDIT: More information on the syntax error I get when I try to pass
public List<string> PartNumberList { get; set; }
to the custom validator class. The class itself is found above. The syntax error reads:
An object reference is required for the non-static field, method, or property 'ReviewOrderModel.PartNumberList'.
I am not sure why the jquery validation fires when the property is nullable int?and not marked with any data annotation attribute. Its asp.net MVC 4 application
public class MyViewModel{
public int? ResourceId { get; set; }
}
MyView.cshtml
#model MyViewModel
#Html.DropDownListFor(m => m.ResourceId,
((IEnumerable<SelectListItem>)ViewData["resourceLookup"]),"--Select--")
Generated HTML
<select data-val="true" data-val-number="The field ResourceId must be a number."
id="ResourceId" name="ResourceId">
<option value="">--Select--</option>
<option value="1">Type 1</option>
</select>
This select box is an optional one. User may ignore it. But when they ignore i am getting error message The field ResourceId must be a number.
In Global.asax.cs, I tried setting AddImplicitRequiredAttributeForValueTypes property to prevent adding the data-* attributes to unwanted fields. But as the name says it prevents only adding data-required-* attributes.
protected void Application_Start()
{
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes=false;
}
How can I prevent the data type checking attribute(data-val-number) here?
EDIT:
Tried setting DataType also no use, still having same issue. It looks like this will not be applied for <select> and may be applied only for <input> and set type="text"
[DataType(DataType.Text)]
public int? ResourceId { get; set; }
I had the same problem and just switched off client-side validation for my dropdown list.
You can deactivate validation for this field by setting data-val = "false":
#Html.DropDownListFor(m => m.ResourceId,
((IEnumerable<SelectListItem>)ViewData["resourceLookup"]),"--Select--", new { #data_val = "false" })
This will not remove the data-val-number attribute but prevent the validation.
When you set the ViewData["resourceLookup"] try adding -1 for the option Select.
Thanks!
I would like to know what is the best approach to handle client-side, javascript or jQuery driven validation of MVC4 fields against attributes placed on a ViewModel's fields.
First, let's pick the example. A login creation screen for Administrators shown the first time the application starts (just not to say to the site owner "use admin/admin as login the first time").
ViewModel:
public class AdministratorViewModel : AbstractViewModel
{
[Required]
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblUsername")]
public string Username { get; set; }
[Required]
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblEmailAddress")]
[EmailAddress]
public string EmailAddress { get; set; }
[Required]
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblPassword")]
[AdminPassword]
public string Password { get; set; }
[Required]
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblPasswordConfirm")]
[Compare("Password")]
public string PasswordConfirm { get; set; }
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblLastLogin")]
public DateTime? LastLogin { get; set; }
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblPasswordExpiry")]
public DateTime? PasswordExpiry { get; set; }
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblBlocked")]
public bool Blocked { get; set; }
}
Partial view (only a few fields needed when creating the first admin)
#using (Html.BeginForm())
{
#Html.ValidationSummary(false)
#Html.AntiForgeryToken()
<fieldset>
<legend>#ManageAdminsViewResources.legendCreateAdmin</legend>
<div class="desktoptile">
#Html.LabelFor(m=>m.Username)
#Html.EditorFor(m => m.Username)
#Html.ValidationMessageFor(m => m.Username)
</div>
<div class="desktoptile">
#Html.LabelFor(m=>m.Password)
#Html.PasswordFor(m => m.Password)
#Html.ValidationMessageFor(m => m.Password)
</div>
<div class="desktoptile">
#Html.LabelFor(m=>m.PasswordConfirm)
#Html.PasswordFor(m => m.PasswordConfirm)
#Html.ValidationMessageFor(m => m.PasswordConfirm)
</div>
<div class="desktoptile">
#Html.LabelFor(m=>m.EmailAddress)
#Html.EditorFor(m => m.EmailAddress)
#Html.ValidationMessageFor(m => m.EmailAddress)
</div>
<input type="submit" value="#ManageAdminsViewResources.btnCreate"/>
</fieldset>
}
Controller
[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateFirst(AdministratorViewModel viewModel)
{
if (!ModelState.IsValid) return View(viewModel);
[...................]
Currently
If I enter an invalid email address, an empty password, etc. in the form and hit Submit I'm correctly notified of the errors. Ok, let's go on
What I want
Since I'm doing a Metro-stylish design, I would like that every time the user unfocuses a text box validation for that field occurs.
Writing hard-coded jQuery fragments is not the best option. I would like a data-driven approach, possibly embedded in MVC4 which I'm currently learning.
So, given a ViewModel with standard and custom attributes (for which, no matter what, a little Javascript is required, think about the Org.Zighinetto.AdminPasswordAttribute that checks password complexity), how do I enforce client-side validation the most unobtrusive way, without specifying client-side tags on each and every html tag and writing the least possible amount of code?
Is still there any secret in ASP.NET MVC 4 validation that I have to unhide?
Well, you would have to invoke jQuery validate using jQuery (because it's written in jQuery :))
You could add a global event for your inputs, then invoke it on the blurred element. Something like:
$("input").blur(function () {
$(this).valid();
});
From my (learning) point of view, the correct answer should be:
Explanation:
Validation of common attributes is done by MVC4 automatically when jQuery Unobtrustive Validator is loaded into the page, otherwise only server-side validation is performed.
Most, if not all of MVC4 ValidationAttributes implement IClientValidation. This interface wraps jQuery Validator validation functions in server-side code. It's hard to explain how exactly it works, but saying that this interface returns the name of the client-side function (either provided by jQuery distribution or implemented by user), while basically wrong at least gives the idea to a novice user trying to understand how validation works.
Answer:
Continue using data-driven model/viewmodel annotations.
Check if NuGet package jQuery Unobtrusive Validation is loaded in the page, then implement IClientValidation as needed (I found a tutorial here about multiple errors), fields are validated automatically.
I have an Ajax.BeginForm() in a partial view that contains a CheckBox for a bool value. The model is as follows;
public class ViewBusinessAdd
{
[Required(ErrorMessage="Name must be supplied")]
[Display(Name = "Business Name")]
public string Name { get; set; }
[Required(ErrorMessage = "Contact must be supplied")]
[Display(Name = "Business Contact")]
public string Contact { get; set; }
[Display(Name = "Phone Number")]
public string Number { get; set; }
public string Postcode { get; set; }
public Dictionary<string, string> States { get; set; }
public string AddressRegion { get; set; }
public bool IsFacebookPost { get; set; }
public List<RecommendationViewAttribute> Attributes { get; set; }
}
The CheckBox is rendered using the Html helpers;
<div class="control-group">
<label class="control-label">
#Html.LabelFor(m => m.IsFacebookPost, "Post recommendation")
<img src="~/Content/images/f_logo.png" alt="Facebook" />
</label>
<div class="controls">
#Html.CheckBoxFor(m => m.IsFacebookPost)
</div>
</div>
This produces the following HTML when rendered;
<input data-val="true" data-val-required="The IsFacebookPost field is required." id="IsFacebookPost" name="IsFacebookPost" type="checkbox" value="true" /><input name="IsFacebookPost" type="hidden" value="false" />
When submitting the form with, it produces this error in Chrome;
Uncaught SyntaxError: Unexpected token u
If I remove the CheckBox the form submits without any error. If I convert this to a non-Ajax form, it also submits but that's not going to work with the page design unfortunately.
I'm absolutely stumped on this - I even changed this to a RadioButton and the same behavior exists. Does anyone have any ideas?
Edit: Forgot to add it's a Javascript error.
Edit: The error is coming from jQuery library on the return below;
parseJSON: function( data ) {
// Attempt to parse using the native JSON parser first
if ( window.JSON && window.JSON.parse ) {
return window.JSON.parse( data );
}
But this only happens if I use the Razor HTML helpers to generate the checkboxes.
I got bit by this and burned about 6 hours trying to figure out what the issue was. According to the Jquery developers this is intended behavior in 1.9.1. If you use the jQuery Migrate 1.1.1 plugin everything should work, other than the console warnings which I think can be turned off.
http://bugs.jquery.com/ticket/13412
See the bug report, which isn't actually a bug :)
Well it seems there was a bug introduced with jQuery 1.9.1. I've downgraded to 1.8.3 and the Razor helpers for the Checkboxes now work correctly. Steps to downgrade if anyone is interested;
Uninstall-Package jQuery -force
Install-Package jQuery -version 1.8.3