2

I have a checkbox validation before i submit the page. But it works in reverse. It's displaying error message when i check the box instead of opposite. I don't know where im doing wrong.

My ViewModel

[Display(Name = "Terms and Conditions")]
[Range(typeof(bool), "true", "true", ErrorMessage = "Please accept Terms & Conditions")]
public bool IsTermsAccepted { get; set; }

My View

<div class="row col-lg-offset-2 top-buffer">
                @Html.CheckBoxFor(model => model.IsTermsAccepted)
                @Html.LabelFor(model => model.IsTermsAccepted)
            <br>@Html.ValidationMessageFor(model => model.IsTermsAccepted)
        </div>

Thank you for your time!

Edit1: I followed exactly like here

Edit2: I was able to resolve this by adding a simple script (as shown in the above mentioned link)

<script>
// extend range validator method to treat checkboxes differently
var defaultRangeValidator = $.validator.methods.range;
$.validator.methods.range = function(value, element, param) {
    if(element.type === 'checkbox') {
        // if it's a checkbox return true if it is checked
        return element.checked;
    } else {
        // otherwise run the default validation function
        return defaultRangeValidator.call(this, value, element, param);
    }
}

Pradvaar cruz
  • 281
  • 1
  • 9
  • 21

1 Answers1

3

You need to understand how CheckBoxFor works in ASP.NET MVC - and how checkboxes work in HTML (see my answer to this question: https://stackoverflow.com/a/11424091/159145 )

  • HTML checkboxes do not post their value in the POST request body if they are not checked.
  • So in order to tell the difference between "checkbox-not-checked" and "checkbox-excluded" you need to include an explicit "false" value as <input type="hidden" /> with the same name="" property value as the checkbox input's.
  • ASP.NET MVC does this for you: Html.CheckBoxFor() renders both an <input type="checkbox" value="true" /> and an <input type="hidden" value="false" />
    • If you look at the rendered HTML of your page, you'll see them both.

so when you submit a checked checkbox, your browser is actually sending two values: true and false. It's the "false" value that causes your Range validator to fail, even though it also sent the true value.

However, the RangeValidatorAttribute is not smart enough to handle this specific case for boolean ViewModel properties set using CheckBoxFor.

As far as I know, there is no built-in DataAnnotation attribute that handles this case - you will need to implement it yourself in your Controller Action, like so:

[HttpPost]
public IHttpActionResult Foo(FooViewModel model) {

    if( !model.IsTermsAccepted ) {

        this.ModelState.AddModelError( nameof(model.IsTermsAccepted), "you must accept the terms." );
        return this.View( model );
    }

}

...or you could try to implement a new validation attribute yourself, it would need to do something like this.

Note it derives from Required attribute, because you need to ensure both that the property has a value in the request body (i.e. that the checkbox was included in the response, with the hidden input) and that the true value is present.

[AttributeUsageAttribute(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
class CheckboxIsCheckedAttribute : RequiredAttribute {

    public override bool IsValid(Object value) {

        Boolean isRequiredValid = base.IsValid( value );
        if( !isRequiredValid ) return false;

        return (value as Boolean) == true;
    }
}
Dai
  • 141,631
  • 28
  • 261
  • 374
  • Good explanation, but note that it should not derive from `RequiredAttribute` - just from `ValidationAttribute` (since its a `bool` its required by default anyway and a validation error will already be added if there is no value in the response). In addition, if you derive from `RequiredAttribute` it also has to be registered in `global.asax` (dazbradbury answer in the dupe is the correct approach and gives client side validation as well) –  Sep 13 '17 at 03:01