19406

MVC Custom Validation for List on Client Side

Question:

I'm trying to write a custom validator that works on the client side that validates that all checkboxes have been ticked.

Here's the declaration on the model:

[DeclarationsAccepted(ErrorMessage = "You must tick all declarations")] public IList<DeclarationQuestion> DeclarationQuestions { get; set; }

And here's the attribute:

public class DeclarationsAccepted : ValidationAttribute, IClientValidatable { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var questions = value as IList<DeclarationQuestion>; if (questions != null && questions.All(c => c.Answer)) { return ValidationResult.Success; } return new ValidationResult("You must accepted all declarations to continue"); } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var modelClientValidationRule = new ModelClientValidationRule { ValidationType = "declarationsaccepted", ErrorMessage = FormatErrorMessage(metadata.DisplayName) }; yield return modelClientValidationRule; } }

So far so good, works server side.

For the client I'm wiring this up as follows:

jQuery.validator.addMethod('declarationsaccepted', function (value, element, params) { //Implement logic here to check all boxes are ticked console.log(value); return false; }, ''); jQuery.validator.unobtrusive.adapters.add('declarationsaccepted', {}, function (options) { options.rules['declarationsaccepted'] = true; options.messages['declarationsaccepted'] = options.message; });

I'm displaying the checkboxes like this:

@{ var questionIndex = 0; } @foreach (var question in Model.DeclarationQuestions) { @Html.CheckBoxFor(model => Model.DeclarationQuestions[questionIndex].Answer, new { id = "DeclarationQuestions" + questionIndex}) questionIndex++; }

And then displaying the validation message using this:

@Html.ValidationMessageFor(c => c.DeclarationQuestions)

When I submit the form the message is displayed but only after a post back to the server. Is there any way to get this to work on the client side?

Answer1:

The reason you will not get client side validation is because the html helpers generate data-val-* attributes for controls associated with properties. jquery.validate.unobtrusive reads those attributes when the form is parsed and using rules, displays an error message in the appropriate element generated by ValidationMessageFor() associated with that control (it does this by matching up the id attributes of the elements - the error message is generated in a span with <span for="TheIdOfTheAssociatedControl" ...>).

You don't (and cant) generate a control for property DeclarationQuestions (only for properties of each item in DeclarationQuestions so there is nothing that can be matched up.

You could handle this by including your own error message placeholder and intercepting the .submit event

html (add css to style #conditions-error as display:none;)

<pre class="lang-html prettyprint-override"><span id="delarations-error" class="field-validation-error"> <span>You must accept all declarations to continue.</span> </span>

Script

var declarationsError = $('#delarations-error'); $('form').submit(function() { var isValid = $('.yourCheckBoxClass').not(':checked') == 0; if(!isValid) { declarationsError.show(); // display error message return false; // prevent submit } }); $('.yourCheckBoxClass').change(function() { if($(this).is(':checked')) { declarationsError.hide(); // hide error message } });

Side note: You loop for generating the checkboxes should be

for (int i = 0; i < Model.DeclarationQuestions.Count; i++) { @Html.CheckBoxFor(m => m.DeclarationQuestions[i].Answer, new { id = "DeclarationQuestions" + i}) }

Recommend

  • cascading dropdown loses select items after post
  • Why can't I find the RadioButtonFor method?
  • Unit Testing null reference exception for controller context
  • Output to another Window
  • Client-side custom annotation validation not working
  • nUnit testing a controller extension method
  • Access HttpConfiguration in Orchard CMS
  • Excel Result not giving me anything, but it runs fine
  • A clean solution to use multiple submit button in ASP.NET MVC
  • “Value cannot be null or empty. Parameter name: contentPath” on a most unexpected line on postback w
  • How can I prevent GCE from copying ssh keys to all new instances?
  • scala : Match type argument for an object
  • Stored Procedure with dynamic result into temp table
  • Selecting one checkbox in loop-generated checkboxes from checkboxlist
  • container engine kubernetes and ssl
  • Openstack.Net SDK cannot access services
  • How to get the revision of an item with Dropbox API
  • Overriding view location for Razor View Engine
  • Are mysqli_result::free and mysqli_stmt::free_result the same?
  • cpan command gives the error “Can't locate B.pm in @INC”
  • Okta SignIn Widget with SAML
  • Easy convert betwen SQLAlchemy columns and data types?
  • Spring SAML Security - Multiple IDP Metadata configuration for two different ADFS server
  • Add filename and length parameter to WCF stream when Transfermode = Stream
  • What to do if “git push heroku master” failed?
  • Using Login with Paypal and using OpenID with AWS Cognito
  • How do I get name of the target table and column of foreign key column with plain JDBC
  • How to Compose OSGi Based project with C++ based project?
  • Heroku push rejected - Hartl's Rails 3.2 tutorial
  • Wrong row deleted from custom listview with spinner
  • How do I formally document a C# Attribute in UML?
  • Spring: No transaction manager has been configured
  • JPA flush vs commit
  • Elasticsearch script query involving root and nested values
  • Why use database factory in asp.net mvc?
  • How do I configure context broker accept post requests from my remote sensor?
  • Read text file and split every line in MSBuild
  • Javascript Callbacks with Object constructor
  • SSO with signing and signature validation doesn't work
  • File not found error Google Drive API