40

If I have a search object with a list of fields, can I, using the System.ComponentModel.DataAnnotations namespace, set it up to validate that at least one of the fields in the search is not null or empty? i.e All the fields are optional but at least one should always be entered.

Justin Johnson
  • 30,978
  • 7
  • 65
  • 89
Boob
  • 1,023
  • 4
  • 13
  • 20

5 Answers5

38

I have extended Zhaph answer to support grouping of properties.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AtLeastOnePropertyAttribute : ValidationAttribute
{
    private string[] PropertyList { get; set; }

    public AtLeastOnePropertyAttribute(params string[] propertyList)
    {
        this.PropertyList = propertyList;
    }

    //See http://stackoverflow.com/a/1365669
    public override object TypeId
    {
        get
        {
            return this;
        }
    }

    public override bool IsValid(object value)
    {
        PropertyInfo propertyInfo;
        foreach (string propertyName in PropertyList)
        {
            propertyInfo = value.GetType().GetProperty(propertyName);

            if (propertyInfo != null && propertyInfo.GetValue(value, null) != null)
            {
                return true;
            }
        }

        return false;
    }
}

Usage:

[AtLeastOneProperty("StringProp", "Id", "BoolProp", ErrorMessage="You must supply at least one value")]
public class SimpleTest
{
    public string StringProp { get; set; }
    public int? Id { get; set; }
    public bool? BoolProp { get; set; }
}

And if you want to have 2 groups (or more):

[AtLeastOneProperty("StringProp", "Id", ErrorMessage="You must supply at least one value")]
[AtLeastOneProperty("BoolProp", "BoolPropNew", ErrorMessage="You must supply at least one value")]
public class SimpleTest
{
    public string StringProp { get; set; }
    public int? Id { get; set; }
    public bool? BoolProp { get; set; }
    public bool? BoolPropNew { get; set; }
}
Valerio Gentile
  • 1,071
  • 11
  • 24
  • 4
    This is great - thank you. It is worth saying that a class-level validation like this is only fired (i.e. IsValid() called) when all the property-level validations pass successfully. – VictorySaber Aug 28 '15 at 14:24
28

I'd create a custom validator for this - it won't give you client side validation, just server side.

Note that for this to work, you'll need to be using nullable types, as value types will default to 0 or false:

First create a new validator:

using System.ComponentModel.DataAnnotations;
using System.Reflection;

// This is a class-level attribute, doesn't make sense at the property level
[AttributeUsage(AttributeTargets.Class)]
public class AtLeastOnePropertyAttribute : ValidationAttribute
{
  // Have to override IsValid
  public override bool IsValid(object value)
  {
    //  Need to use reflection to get properties of "value"...
    var typeInfo = value.GetType();

    var propertyInfo = typeInfo.GetProperties();

    foreach (var property in propertyInfo)
    {
      if (null != property.GetValue(value, null))
      {
        // We've found a property with a value
        return true;
      }
    }

    // All properties were null.
    return false;
  }
}

You can then decorate your models with this:

[AtLeastOneProperty(ErrorMessage="You must supply at least one value")]
public class SimpleTest
{
    public string StringProp { get; set; }
    public int? Id { get; set; }
    public bool? BoolProp { get; set; }
}

Then when you call ModelState.IsValid your validator will be called, and your message will be added to the ValidationSummary on your view.

Note that you could extend this to check for the type of property coming back, or look for attributes on them to include/exclude from validation if you want to - this is assuming a generic validator that doesn't know anything about the type it's validating.

Zhaph - Ben Duguid
  • 26,785
  • 5
  • 80
  • 117
  • 1
    +1 Nice. I didn't know adding class level attributes was possible with DataAnnotations. – Steven Apr 26 '10 at 14:52
  • This looks really nice, I'll get back to this before the end of the week and give your answer a go. I'll let you know how it turns out! thanks – Boob Apr 27 '10 at 07:58
  • @JefClaes - True: the answer was accepted before the comment, so we have no real idea if it worked completely or not, I take silence as implicit acceptance ;) – Zhaph - Ben Duguid Nov 08 '12 at 15:23
  • 3
    That's a nice solution; but I think more useful would be to allow for grouping of properties at the property level... rather than saying for all properties of a class at least one must be set; one could have two, three related groups where at least one value within those groups must be set – Dave Lawrence May 10 '13 at 09:35
  • Darin has a better solution hrer http://stackoverflow.com/questions/8906228/how-to-validate-one-field-related-to-anothers-value-in-asp-net-mvc-3 – RasikaSam Jan 16 '14 at 04:12
  • 2
    I'm not sure Darin's solution is "better" in this context - Boob asked about "a list of fields [...] that at least one is not empty", not just two - However either could be extended to either: 1. (Darin) Take a list of fields that must have a value. 2. (mine) have some annotations on the properties and check for those too - enabling you to have fields that aren't in the "required" group (as daveL suggests). – Zhaph - Ben Duguid Jan 16 '14 at 09:47
3

This question is pretty old, but as of .NET 3.5 (I believe), IValidatableObject can help with tricky validation situations. You can implement it to validate arbitrary business rules. In this case, something like:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    if (string.IsNullOrWhiteSpace(FieldOne) && string.IsNullOrWhiteSpace(FieldTwo))
        yield return new ValidationResult("Must provide value for either FieldOne or FieldTwo.", new string[] { "FieldOne", "FieldTwo" });
}
fordareh
  • 2,923
  • 2
  • 26
  • 39
1

Validation in .net checks properties. Let's say you have two string properties, field1 and field2. Just add a property like this.

[RegularExpression("True|true", ErrorMessage = "At least one field must be given a value")] 
public bool Any => field1 != null || field2 != null;

Details here, with additional validations like MinLength etc: https://stackoverflow.com/a/69621414/6742644

0

If you want to do complex validation against any .Net class, without litering them with annotations, look at FluentValidation, or for .Net 2.0, FluentValidation for 2.0

Daniel Dyson
  • 13,192
  • 6
  • 42
  • 73
  • 1
    Daniel, thanks for the answer but as we have been using the annotations successfully thus far in the project and it has been very good for what we have needed, I want to try to stick to this method of validation. This is the first real stumbling point thus far and I'm hoping someone may have a nice solution for me! :) – Boob Apr 26 '10 at 10:38
  • No worries. When you get time, check out the links. You will find the Fluent approach to be intuitive and easy to use. And very powerful. – Daniel Dyson Apr 26 '10 at 10:40
  • That's really opinion-based :) – zaitsman Jan 10 '18 at 01:39
  • 1
    Hi @zaitsman do you mean the word "littering"? Maybe you are right. My answer was written nearly 8 years ago. :) Even after all this time, I find it easier to inject a derivative of a FluentValidation abstractvalidator. Especially when doing complex validation against a cached collection or repository lookup – Daniel Dyson Jan 17 '18 at 07:28