50
votes

I've got a view model like this:

public class SignUpViewModel
{
    [Required(ErrorMessage = "Bitte lesen und akzeptieren Sie die AGB.")]
    [DisplayName("Ich habe die AGB gelesen und akzeptiere diese.")]
    public bool AgreesWithTerms { get; set; }
}

The view markup code:

<%= Html.CheckBoxFor(m => m.AgreesWithTerms) %>
<%= Html.LabelFor(m => m.AgreesWithTerms)%>

The result:

No validation is executed. That's okay so far because bool is a value type and never null. But even if I make AgreesWithTerms nullable it won't work because the compiler shouts

"Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions."

So, what's the correct way to handle this?

12

12 Answers

96
votes

My Solution is as follows (it's not much different to the answers already submitted, but I believe it's named better):

/// <summary>
/// Validation attribute that demands that a boolean value must be true.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class MustBeTrueAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        return value != null && value is bool && (bool)value;
    }
}

Then you can use it like this in your model:

[MustBeTrue(ErrorMessage = "You must accept the terms and conditions")]
[DisplayName("Accept terms and conditions")]
public bool AcceptsTerms { get; set; }
51
votes

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.

19
votes

I got it by creating a custom attribute:

public class BooleanRequiredAttribute : RequiredAttribute 
{
    public override bool IsValid(object value)
    {
        return value != null && (bool) value;
    }
}
7
votes
[Compare("Remember", ErrorMessage = "You must accept the terms and conditions")]
public bool Remember { get; set; }
6
votes

This might be a "hack" but you can use the built in Range attribute:

[Display(Name = "Accepted Terms Of Service")]
[Range(typeof(bool), "true", "true")]
public bool Terms { get; set; }

The only problem is the "warning" string will say "The FIELDNAME must be between True and true".

4
votes

"Required" is the wrong validation, here. You want something akin to "Must have the value true," which is not the same as "Required". What about using something like:

[RegularExpression("^true")]

?

3
votes

My solution is this simple custom attribute for boolean values:

public class BooleanAttribute : ValidationAttribute
{
    public bool Value
    {
        get;
        set;
    }

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

Then you can use it like this in your model:

[Required]
[Boolean(Value = true, ErrorMessage = "You must accept the terms and conditions")]
[DisplayName("Accept terms and conditions")]
public bool AcceptsTerms { get; set; }
3
votes

I'm just taking the best of the existing solutions and putting it together into a single answer that allows for both server side and client side validation.

The to apply to model a properties to ensure a bool value must be true:

/// <summary>
/// Validation attribute that demands that a <see cref="bool"/> value must be true.
/// </summary>
/// <remarks>Thank you <c>http://stackoverflow.com/a/22511718</c></remarks>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class MustBeTrueAttribute : ValidationAttribute, IClientValidatable
{
    /// <summary>
    /// Initializes a new instance of the <see cref="MustBeTrueAttribute" /> class.
    /// </summary>
    public MustBeTrueAttribute()
        : base(() => "The field {0} must be checked.")
    {
    }

    /// <summary>
    /// Checks to see if the given object in <paramref name="value"/> is <c>true</c>.
    /// </summary>
    /// <param name="value">The value to check.</param>
    /// <returns><c>true</c> if the object is a <see cref="bool"/> and <c>true</c>; otherwise <c>false</c>.</returns>
    public override bool IsValid(object value)
    {
        return (value as bool?).GetValueOrDefault();
    }

    /// <summary>
    /// Returns client validation rules for <see cref="bool"/> values that must be true.
    /// </summary>
    /// <param name="metadata">The model metadata.</param>
    /// <param name="context">The controller context.</param>
    /// <returns>The client validation rules for this validator.</returns>
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        if (metadata == null)
            throw new ArgumentNullException("metadata");
        if (context == null)
            throw new ArgumentNullException("context");

        yield return new ModelClientValidationRule
            {
                ErrorMessage = FormatErrorMessage(metadata.DisplayName),
                ValidationType = "mustbetrue",
            };
    }
}

The JavaScript to include to make use of unobtrusive validation.

jQuery.validator.addMethod("mustbetrue", function (value, element) {
    return element.checked;
});
jQuery.validator.unobtrusive.adapters.addBool("mustbetrue");
2
votes

For people who are having trouble getting this working for validation on the client side (formerly me): make sure you have also

  1. Included <% Html.EnableClientValidation(); %> before the form in the view
  2. Used <%= Html.ValidationMessage or Html.ValidationMessageFor for the field
  3. Created a DataAnnotationsModelValidator which returns a rule with a custom validation type
  4. Registered the class deriving from DataAnnotationsModelValidator in the Global.Application_Start

http://www.highoncoding.com/Articles/729_Creating_Custom_Client_Side_Validation_in_ASP_NET_MVC_2_0.aspx

is a good tutorial on doing this, but misses step 4.

2
votes

The proper way to do this is to check the type!

[Range(typeof(bool), "true", "true", ErrorMessage = "You must or else!")]
public bool AgreesWithTerms { get; set; }
1
votes

Found a more complete solution here (both server and client side validation):

http://blog.degree.no/2012/03/validation-of-required-checkbox-in-asp-net-mvc/#comments

1
votes

It's enough to add [RegularExpression]:

[DisplayName("I accept terms and conditions")]
[RegularExpression("True", ErrorMessage = "You must accept the terms and conditions")]
public bool AgreesWithTerms { get; set; }

Note - "True" must start with capital T