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?

13 Answers 13

100

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; }
Sign up to request clarification or add additional context in comments.

8 Comments

+1: Searched, found the post, picked the best answer (you're right, yours is named best), cut, pasted, refresh, problem solved. Less than 2 minutes...sweet
This doesn't seem to be working on my vanilla MVC 2 project...known issue?
Also an excellent example, if it doesn't seem to be working for you, it seems to wait until all of your other validation ([Required] etc...) is true before "firing".
The first check (value != null) can even be omitted :)
Doesn't work client-side? This is the same as just putting a [Range(1, 1)] annotation on.
|
52

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.

8 Comments

At last I have found working solution! Others are not complete (sometimes JS part is missed, sometimes it's wrong). Thank you very much.
@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.
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.
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.
This should be the accepted answer, the above one and accepted one dont work clientside
|
20

I got it by creating a custom attribute:

public class BooleanRequiredAttribute : RequiredAttribute 
{
    public override bool IsValid(object value)
    {
        return value != null && (bool) value;
    }
}

2 Comments

+1 for posting your solution, but I still think "Required" is the wrong name. I'd call it BooleanRequireTrueAttribute or something.
Yes, I agree with you. I'll rename it to BooleanRequireToBeTrueAttribute
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".

3 Comments

Works fine for me, and I dont see it as a hack
You may need to add client code validation to make it works. <script> var defaultRangeValidator = $.validator.methods.range; $.validator.methods.range = function(value, element, param) { if(element.type === 'checkbox') { return element.checked; } else { return defaultRangeValidator.call(this, value, element, param); } } </script> Read here for more info
Doesn't work client side, shows the error message when the checkbox is checked
7
[Compare("Remember", ErrorMessage = "You must accept the terms and conditions")]
public bool Remember { get; set; }

1 Comment

Nice one :) But I suspect this won't work server side?
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");

Comments

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

?

5 Comments

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, it will be on your edit model, but not on your entities. Generally, you shouldn't bind directly to entities.
@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?
Right, but that isn't what he proposed here.
@Peter: Examine what the POST contains. Write a regex which is better than my first guess.
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; }

Comments

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; }

Comments

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.

Comments

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

Comments

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

Comments

1

If you are using s1mm0t's answer for .NET Core 6 and higher you need to also add a required tag, so:

Register.cshtml.cs:

public class RegisterModel : PageModel{
...
 /// <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;
            }
        }
    ...

}

and

public class InputModel{
...
[Required]
            [MustBeTrue(ErrorMessage = "You must accept the terms and conditions\n")]
            [DisplayName("Accept terms and conditions")]
            public bool AcceptsTerms { get; set; }
}

Register.cshtml:

<p>
                    <input id="AcceptTermsCheckbox" asp-for="Input.AcceptsTerms" />
                    <label asp-for="Input.AcceptsTerms"></label>
                    <span asp-validation-for="Input.AcceptsTerms" class="text-danger"></span>
                    By creating an account you confirm that you have read and agree with our Terms & Conditions and Privacy Policy
                </p>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.