93

Is there a way through data annotations to require that a boolean property be set to true?

public class MyAwesomeObj{
    public bool ThisMustBeTrue{get;set;}
}
Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
Marty Trenouth
  • 3,712
  • 6
  • 34
  • 43

17 Answers17

132

I would create a validator for both Server AND Client side. Using MVC and unobtrusive form validation, this can be achieved simply by doing the following:

Firstly, create a class in your project to perform the server side validation like so:

public class EnforceTrueAttribute : ValidationAttribute, IClientValidatable
{
    public override bool IsValid(object value)
    {
        if (value == null) return false;
        if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties.");
        return (bool)value == true;
    }

    public override string FormatErrorMessage(string name)
    {
        return "The " + name + " field must be checked in order to continue.";
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ErrorMessage = String.IsNullOrEmpty(ErrorMessage) ? FormatErrorMessage(metadata.DisplayName) : ErrorMessage,
            ValidationType = "enforcetrue"
        };
    }
}

Following this, annotate the appropriate property in your model:

[EnforceTrue(ErrorMessage=@"Error Message")]
public bool ThisMustBeTrue{ get; set; }

And Finally, enable client side validation by adding the following script to your View:

<script type="text/javascript">
    jQuery.validator.addMethod("enforcetrue", function (value, element, param) {
        return element.checked;
    });
    jQuery.validator.unobtrusive.adapters.addBool("enforcetrue");
</script>

Note: We already created a method GetClientValidationRules which pushes our annotation to the view from our model.

If using resource files to supply the error message for internationalization, remove the FormatErrorMessage call (or just call the base) and tweak the GetClientValidationRules method like so:

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
    string errorMessage = String.Empty;
    if(String.IsNullOrWhiteSpace(ErrorMessage))
    {
        // Check if they supplied an error message resource
        if(ErrorMessageResourceType != null && !String.IsNullOrWhiteSpace(ErrorMessageResourceName))
        {
            var resMan = new ResourceManager(ErrorMessageResourceType.FullName, ErrorMessageResourceType.Assembly);
            errorMessage = resMan.GetString(ErrorMessageResourceName);
        }
    }
    else
    {
        errorMessage = ErrorMessage;
    }

    yield return new ModelClientValidationRule
    {
        ErrorMessage = errorMessage,
        ValidationType = "enforcetrue"
    };
}
akousmata
  • 1,005
  • 14
  • 34
dazbradbury
  • 5,729
  • 5
  • 34
  • 38
  • 3
    Thanks for this - it works great! It works better with the override FormatErrorMessage method removed - that way localisation of error messages from Resource files works. My usage: [EnforceTrue(ErrorMessageResourceType = typeof(ValidationMessages), ErrorMessageResourceName = "TermsAndConditionsRequired")] – Matt Frear Jan 17 '14 at 12:19
  • 2
    I can't get the client side validation to work and can't seem to tell what I'm doing wrong. Where exactly should I put the javacsript? In the head tag? Next to the controller? – Misbit Jan 30 '14 at 10:09
  • 1
    Great solution showing the power of custom validation attributes! Though I recommend putting the script in a globally referenced js file, not the view(s), for reuse. Also, best to handle all ways the message strings could be added: default if none provided, or the message string, or from a resource file. – jeepwran Dec 10 '14 at 17:45
  • How would I do this without yield VB.net 2012 is not recognizing that key word and it is driving me crazy as I just wanted to do one simple thing and now I am down a rabbit hole. – Julie Feb 16 '15 at 23:14
  • 1
    Great solution, thanks for posting. For those who can't get the client side valdation to work: You have to extend jQuery validation before the controls that it will validate have been loaded, so put the script in the head, and not in the window.onload / $(document).ready() event. – Evert Sep 17 '15 at 21:51
98

I know this is an older post but wanted to share a simple server side way to do this. You create a public property set to true and compare your bool to that property. If your bool is not checked (by default false) the form will not validate.

public bool isTrue
{ get { return true; } }

[Required]
[Display(Name = "I agree to the terms and conditions")]
[Compare("isTrue", ErrorMessage = "Please agree to Terms and Conditions")]
public bool AgreeTerms { get; set; }

Razor code

@Html.CheckBoxFor(m => Model.AgreeTerms, new { id = "AgreeTerms", @checked = "checked" })
<label asp-for="AgreeTerms" class="control-label"></label>
<a target="_blank" href="/Terms">Read</a>
<br />
@Html.ValidationMessageFor(model => model.AgreeTerms, "", new { @class = "text-danger" })
@Html.HiddenFor(x => Model.isTrue)
Owen Johnson
  • 2,416
  • 2
  • 19
  • 23
fields.cage
  • 1,643
  • 12
  • 8
54

You could create your own validator:

public class IsTrueAttribute : ValidationAttribute
{
    #region Overrides of ValidationAttribute

    /// <summary>
    /// Determines whether the specified value of the object is valid. 
    /// </summary>
    /// <returns>
    /// true if the specified value is valid; otherwise, false. 
    /// </returns>
    /// <param name="value">The value of the specified validation object on which the <see cref="T:System.ComponentModel.DataAnnotations.ValidationAttribute"/> is declared.
    ///                 </param>
    public override bool IsValid(object value)
    {
        if (value == null) return false;
        if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties.");

        return (bool) value;
    }

    #endregion
}
moribvndvs
  • 42,191
  • 11
  • 135
  • 149
  • I'd consider enhancing this with a client side implementation - rather than using the remote validation referred to in other answers, use the unobtrusive spelt out here: http://www.jacopretorius.net/2011/01/client-side-validation-in-mvc-3.html – SamStephens Oct 10 '11 at 22:22
  • This is a good (and tested) quick solution for us. We can do without the client side validation in @dazbradbury's solution (also a good one) because we only need this on a lone checkbox on the past page of a survey. – Seth May 30 '13 at 17:41
  • `return (bool) value == true;` this is a redundant comparison – T-moty Apr 03 '17 at 10:30
26

I tried several solutions but none of them worked completely for me to get both client and server side validation. So what I did in my MVC 5 application to get it to work:

In your ViewModel (for server side validation):

public bool IsTrue => true;

[Required]
[Display(Name = "I agree to the terms and conditions")]
[Compare(nameof(IsTrue), ErrorMessage = "Please agree to Terms and Conditions")]
public bool HasAcceptedTermsAndConditions { get; set; }

In your Razor page (for client side validation):

<div class="form-group">
   @Html.CheckBoxFor(m => m.HasAcceptedTermsAndConditions)
   @Html.LabelFor(m => m.HasAcceptedTermsAndConditions)
   @Html.ValidationMessageFor(m => m.HasAcceptedTermsAndConditions)

   @Html.Hidden(nameof(Model.IsTrue), "true")
</div>
Kapé
  • 4,411
  • 3
  • 37
  • 54
10

I would just like to direct people to the following Fiddle: https://dotnetfiddle.net/JbPh0X

The user added [Range(typeof(bool), "true", "true", ErrorMessage = "You gotta tick the box!")] to their boolean property which causes server side validation to work.

In order to also have the client side validation working, they added the following script:

// extend jquery range validator to work for required checkboxes
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);
    }
}
Ruskin
  • 5,721
  • 4
  • 45
  • 62
harvzor
  • 2,832
  • 1
  • 22
  • 40
9

Just check to see whether its string representation is equal to True:

[RegularExpression("True")]
public bool TermsAndConditions { get; set; }
ta.speot.is
  • 26,914
  • 8
  • 68
  • 96
5

You could either create your own attribute or use the CustomValidationAttribute.

This is how you would use the CustomValidationAttribute:

[CustomValidation(typeof(BoolValidation), "ValidateBool")]

where BoolValidation is defined as:

public class BoolValidation
{
  public static ValidationResult ValidateBool(bool boolToBeTrue)
  {
    if (boolToBeTrue)
    {
      return ValidationResult.Success;
    }
    else
    {
      return new ValidationResult(
          "Bool must be true.");
    }
  }
Matthew Manela
  • 16,572
  • 3
  • 64
  • 66
5

[Required] attribute stands for requiring any value - it can be either true or false. You'd have to use another validation for that.

George Stocker
  • 57,289
  • 29
  • 176
  • 237
Sergey Kudriavtsev
  • 10,328
  • 4
  • 43
  • 68
3

Following up on the post by ta.speot.is and the comment from Jerad Rose:

The given post will not work client-side with unobtrusive validation. This should work in both camps (client & server):

[RegularExpression("(True|true)")]
public bool TermsAndConditions { get; set; }
david thompson
  • 110
  • 1
  • 10
lobotommy
  • 91
  • 6
  • Don't know if this is a newer version issue, but it's not working for me with jquery.validate 1.19.2 and jquery.validate.unobtrusive 3.2.11. The problem seems to be the `regex` method unobtrusive defines first checks whether the checkbox is optional before validating the regex, which makes sense, except that jquery.validate appears to consider any unchecked checkbox to be optional. tl;dr It only runs the regex on checked checkboxes. We can add a shim for the `regex` `validator` method or just create a custom validator. – xr280xr Jul 13 '20 at 19:32
3

For ASP.NET Core MVC here is client and server validation, based on dazbradbury's solution

public class EnforceTrueAttribute : ValidationAttribute, IClientModelValidator
{
    public override bool IsValid(object value)
    {
        if (value == null) return false;
        if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties.");
        return (bool)value;
    }

    public void AddValidation(ClientModelValidationContext context)
    {
        MergeAttribute(context.Attributes, "data-val", "true");
        var errorMessage = ErrorMessage ?? 
            $"The value for field {context.ModelMetadata.GetDisplayName()} must be true.";
        MergeAttribute(context.Attributes, "data-val-enforcetrue", errorMessage);
    }

    private void MergeAttribute(IDictionary<string, string> attributes,
        string key,
        string value)
    {
        if (attributes.ContainsKey(key))
        {
            return;
        }
        attributes.Add(key, value);
    }
}

And then on the client:

$.validator.addMethod("enforcetrue", function (value, element, param) {
    return element.checked;
});

$.validator.unobtrusive.adapters.addBool("enforcetrue");

Then usage is:

[EnforceTrue(ErrorMessage = "Please tick the checkbox")]
public bool IsAccepted { get; set; }
Matt
  • 1,446
  • 19
  • 17
  • When use this solution, consider [this](https://github.com/dotnet/AspNetCore.Docs/issues/7894). Put the javascript code outside any "jquery `$document.ready()`/`$(function() { });`". – Igor Nov 23 '20 at 17:05
  • Another tip, NOT put `required` attribute on HTML input, like: `` – Igor Nov 23 '20 at 18:38
2

This is what worked for me. Nothing else did. Mvc 5:

Model

public string True
{
  get
  {
    return "true";
  }
}

[Required]
[Compare("True", ErrorMessage = "Please agree to the Acknowlegement")]
public bool Acknowlegement { get; set; }

View

  @Html.HiddenFor(m => m.True)
  @Html.EditorFor(model => model.Acknowlegement, new { htmlAttributes = Model.Attributes })
  @Html.ValidationMessageFor(model => model.Acknowlegement, "", new { @class = "text-danger" })

enter image description here

enter image description here

toddmo
  • 20,682
  • 14
  • 97
  • 107
2

I don't know of a way through DataAnnotations, but this is easily done in your controller.

public ActionResult Add(Domain.Something model)
{

    if (!model.MyCheckBox)
        ModelState.AddModelError("MyCheckBox", "You forgot to click accept");

    if (ModelState.IsValid) {
        //'# do your stuff
    }

}

The only other option would be to build a custom validator for the server side and a remote validator for the client side (remote validation is only available in MVC3+)

Chase Florell
  • 46,378
  • 57
  • 186
  • 376
2

Do you have the appropriate items set up in the web.config?

That could cause the validation not to work.

You can also try to create a custom validation attribute (since [Required] only cares whether or not it exists, and you care about the value):

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class RequiredTrueAttribute : ValidationAttribute
{
    // Internal field to hold the mask value.
    readonly bool accepted;

    public bool Accepted
    {
        get { return accepted; }
    }

    public RequiredTrueAttribute(bool accepted)
    {
        this.accepted = accepted;
    }

    public override bool IsValid(object value)
    {
        bool isAccepted = (bool)value;
        return (isAccepted == true);
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentCulture,
          ErrorMessageString, name, this.Accepted);
    }
}

Then, usage:

[RequiredTrue(ErrorMessage="{0} requires acceptance to continue.")]
public bool Agreement {get; set;}

From here.

Community
  • 1
  • 1
George Stocker
  • 57,289
  • 29
  • 176
  • 237
1

I tried to use fields.cage's answer and it didn't quite work for me, but something simpler did, and I'm not sure exactly why (different Razor version, maybe?), but all I had to do was this:

[Required]
[Range(typeof(bool), "true", "true", ErrorMessage = "Agreement required.")]
[Display(Name = "By clicking here, I agree that my firstborn child will etc etc...")]
public bool Agreement1Checked { get; set; }

And in the .cshtml file:

@Html.CheckBoxFor(m => m.Agreement1Checked)
@Html.LabelFor(m => m.Agreement1Checked)
@Html.ValidationMessageFor(m => m.Agreement1Checked)
Dronz
  • 1,970
  • 4
  • 27
  • 50
  • This doesn't work client-side for me. For some reason the parameter passed to the jquery.validate rule method is `[NaN, NaN]` where it should be `[true, true]` – xr280xr Apr 27 '20 at 04:27
  • @xr280xr Even when the user has checked the checkbox? – Dronz Apr 27 '20 at 04:43
0

I think the best way to handle this is just check in your controller if the box is true otherwise just add an error to your model and have it redisplay your view.

As previously stated all [Required] does is make sure there is a value and in your case if not checked you still get false.

samack
  • 815
  • 11
  • 28
-1
/// <summary> 
///  Summary : -CheckBox for or input type check required validation is not working the root cause and solution as follows
///
///  Problem :
///  The key to this problem lies in interpretation of jQuery validation 'required' rule. I digged a little and find a specific code inside a jquery.validate.unobtrusive.js file:
///  adapters.add("required", function (options) {
///  if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") {
///    setValidationValues(options, "required", true);
///    }
///   });
///   
///  Fix: (Jquery script fix at page level added in to check box required area)
///  jQuery.validator.unobtrusive.adapters.add("brequired", function (options) {
///   if (options.element.tagName.toUpperCase() == "INPUT" && options.element.type.toUpperCase() == "CHECKBOX") {
///              options.rules["required"] = true;
///   if (options.message) {
///                   options.messages["required"] = options.message;
///                       }
///  Fix : (C# Code for MVC validation)
///  You can see it inherits from common RequiredAttribute. Moreover it implements IClientValidateable. This is to make assure that rule will be propagated to client side (jQuery validation) as well.
///  
///  Annotation example :
///   [BooleanRequired]
///   public bool iAgree { get; set' }
/// </summary>


public class BooleanRequired : RequiredAttribute, IClientValidatable
{

    public BooleanRequired()
    {
    }

    public override bool IsValid(object value)
    {
        return value != null && (bool)value == true;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        return new ModelClientValidationRule[] { new ModelClientValidationRule() { ValidationType = "brequired", ErrorMessage = this.ErrorMessage } };
    }
}
Pang
  • 9,564
  • 146
  • 81
  • 122
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – Ravi Dhoriya ツ Dec 31 '14 at 09:30
  • It works Check this link with reason why it fails on validation-http://itmeze.com/2010/12/06/checkbox-has-to-be-checked-with-unobtrusive-jquery-validation-and-asp-net-mvc-3 – dhandapani harikrishnan Dec 31 '14 at 10:02
  • Today it works. Can you be sure it will stay working in 5, 10 years later? These Q&A DB is created for future users too – Prophet Dec 31 '14 at 15:41
-1

Check out Foolproof validation here. You can download/install it via Nuget.

It's a great little library for this kind of thing.

DavidWainwright
  • 2,895
  • 1
  • 27
  • 30