52

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?

asp_net
  • 3,567
  • 3
  • 31
  • 58

12 Answers12

99

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; }
s1mm0t
  • 6,035
  • 4
  • 38
  • 45
51

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.

dazbradbury
  • 5,729
  • 5
  • 34
  • 38
  • 3
    At last I have found working solution! Others are not complete (sometimes JS part is missed, sometimes it's wrong). Thank you very much. – Alexander Prokofyev Mar 26 '12 at 10:02
  • @AlexanderProkofyev - Glad you found it useful! I found the same thing, so once I had a solution, I thought I'd post a complete answer. – dazbradbury Mar 26 '12 at 17:06
  • It's probably worth noting: If want to separate your JS from your views, calling the addMethod and addBool functions to set up the client-side validation does not work from inside document.ready functions. – JBarnes Apr 17 '13 at 16:32
  • Excellent! Also worth noting--if it doesn't seem to be working, all of the other validation has to pass before this one will get validated. – Chad Hedgcock Jan 15 '14 at 15:26
  • @Chad-Hedgcock This means that the validation Works on the server-side, after the form is submitted. – LoBo Oct 14 '14 at 14:55
  • 1
    This should be the accepted answer, the above one and accepted one dont work clientside – Owen Oct 02 '15 at 12:02
  • This is an excellent answer, but how would the client validation be modified to allow the EnforceTrue attribute on multiple properties in the model? If one is invalid all others are also highlighted on the client as invalid. – Yetiish Oct 23 '15 at 08:48
20

I got it by creating a custom attribute:

public class BooleanRequiredAttribute : RequiredAttribute 
{
    public override bool IsValid(object value)
    {
        return value != null && (bool) value;
    }
}
asp_net
  • 3,567
  • 3
  • 31
  • 58
8

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".

ProVega
  • 5,864
  • 2
  • 36
  • 34
  • 1
    Works fine for me, and I dont see it as a hack – Owen Oct 02 '15 at 14:19
  • 1
    You may need to add client code validation to make it works. `` [Read here for more info](http://jasonwatmore.com/post/2013/10/16/ASPNET-MVC-Required-Checkbox-with-Data-Annotations.aspx) – Chun Lin May 02 '16 at 10:26
  • 1
    Doesn't work client side, shows the error message when the checkbox is checked – garryp Jul 25 '17 at 12:42
7
[Compare("Remember", ErrorMessage = "You must accept the terms and conditions")]
public bool Remember { get; set; }
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Vedat Taylan
  • 136
  • 1
  • 2
6

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");
James Skimming
  • 4,991
  • 4
  • 26
  • 32
4

"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")]

?

Craig Stuntz
  • 125,891
  • 12
  • 252
  • 273
  • If a boolean must always be / is required to be true, it's not even necessary in your code. If the Required attribute is there to help with the DB level "NOT NULL" statement, it's not needed because you're guaranteed to have either a true or false value. – Chris F Feb 11 '10 at 15:19
  • 1
    @Chris, it will be on your edit model, but not on your entities. Generally, you shouldn't bind directly to entities. – Craig Stuntz Feb 11 '10 at 15:25
  • @Craig, agreed on not directly binding. However, if your domain model object (entity) has a property that is required to always be true, then it's not a property that is of any use, right? – Chris F Feb 11 '10 at 19:12
  • Right, but that isn't what he proposed here. – Craig Stuntz Feb 11 '10 at 19:41
  • 1
    @Peter: Examine what the POST contains. Write a regex which is better than my first guess. – Craig Stuntz Mar 11 '10 at 13:41
3

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; }
Maksymilian Majer
  • 2,956
  • 2
  • 29
  • 42
3

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; }
fuzzybear
  • 2,325
  • 3
  • 23
  • 45
2

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.

Craig Lebowitz
  • 549
  • 4
  • 6
1

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

user1608132
  • 301
  • 5
  • 13
1

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

mvt
  • 11
  • 1