user7158437
user7158437

Reputation:

c# How to Add Custom Validation Based on Boolean

Here i have a Class Like

Public Class Employee
{
       [Required]
        public string FName { get; set; }
        [Required]
        public bool Married { get; set; }

        public string WifeName { get; set; }
        public string Wife_dOB { get; set; }

}

Here WifeName And Wife_Dob is Required only When Married Is True Please Help me How can i Resolve this Problem Im using Here MVC

Upvotes: 0

Views: 3521

Answers (2)

coding.monkey
coding.monkey

Reputation: 226

FluentValidation might be a good choice for your problem. With FluentValidation it would be easy to create an EmployeeValidator class to perform as much or as little validation as needed.

Note: The examples below are written using the standard FluentValidation package since I don't use MVC. There appears to be nuget packages for several versions of MVC in addition to the standard nuget package and your mileage may vary using those implementations.


Here is a very simple example of how to use FluentValidation:

void Main()
{
    var emp = new Employee { FName = "John Smith", Married = true };
    var val = new EmployeeValidator();
    val.ValidateAndThrow(emp);
}

public class Employee
{
    public string FName { get; set; }
    public bool Married { get; set; }
    public string WifeName { get; set; }
    public string Wife_dOB { get; set; }
}

public class EmployeeValidator : AbstractValidator<Employee>
{
    public EmployeeValidator()
    {
        RuleFor(e => e.WifeName).NotEmpty().When(e => e.Married);
        RuleFor(e => e.Wife_dOB).NotEmpty().When(e => e.Married);
    }
}

EDIT I forgot to include an example of the output from the validation code above.

Example output:

ValidationException: Validation failed: -- WifeName: 'Wife Name' must not be empty. -- Wife_dOB: 'Wife_d OB' must not be empty.


An alternative approach, and in my opinion a better one, would be something like this that requires no validation, at least for the Married property:

public class Person
{
    public Person(string firstName, string surname, DateTime dateOfBirth)
    {
        FirstName = firstName;
        Surname = surname;
        DateOfBirth = dateOfBirth;
    }

    public string FirstName { get; set; }
    public string Surname { get; set; }
    public string FullName => $"{FirstName} {Surname}";
    public DateTime DateOfBirth { get; }
}

public class Employee : Person
{
    public Employee(string firstName, string lastName, DateTime dateOfBirth, DateTime dateOfHire)
        : base(firstName, lastName, dateOfBirth)
        => DateOfHire = dateOfHire;

    public DateTime DateOfHire { get; }
    public Person Spouse { get; set; }
    public bool IsMarried => Spouse != null;
}

With this implementation the IsMarried property simply projects whether or not the Spouse property is set. This is purely for convenience but can often be helpful.


Some validation that might make sense for these objects could be the following:

public class PersonValidator : AbstractValidator<IPerson>
{
    public PersonValidator()
    {
        RuleFor(p => p.FirstName).NotEmpty();
        RuleFor(p => p.Surname).NotEmpty();
        RuleFor(p => p.DateOfBirth).SetValidator(new DateOfBirthValidator());
    }
}

public class EmployeeValidator : AbstractValidator<IEmployee>
{
    private static readonly DateTime CompanyInceptionDate
        = DateTime.Today.Subtract(TimeSpan.FromDays(365.25d * 10d));

    public EmployeeValidator()
    {
        // Person rules
        RuleFor(p => p.FirstName).NotEmpty();
        RuleFor(p => p.Surname).NotEmpty();
        RuleFor(p => p.DateOfBirth).SetValidator(new DateOfBirthValidator());

        // Employee rules
        RuleFor(e => e.DateOfHire).SetValidator(
            // Can't be in the future nor older than the company
            new DateRangeValidator(CompanyInceptionDate, DateTime.Today));
    }
}

// This class really isn't necessary
// The built-in InclusiveBetween/ExclusiveBetween validators would work just as well
public class DateRangeValidator : PropertyValidator
{
    protected const double DaysInAYear = 365.25d;

    public DateRangeValidator(DateTime from, DateTime to)
        : base($"{{PropertyName}} out of range. Expected between {from:yyyy-MM-dd} and {to:yyyy-MM-dd}.")
    {
        From = from;
        To = to;
    }

    public DateTime From { get; }
    public DateTime To { get; }

    protected override bool IsValid(PropertyValidatorContext context)
        => context.PropertyValue is DateTime date
            ? date >= From && date <= To
            : false;
}

public class DateOfBirthValidator : DateRangeValidator
{
    private static readonly TimeSpan OneHundredAndFiftyYears
        = TimeSpan.FromDays(DaysInAYear * 150d);

    public DateOfBirthValidator()
        // Can't be in the future nor older than 150 years
        : base(DateTime.Today.Subtract(OneHundredAndFiftyYears), DateTime.Today) { }
}

Upvotes: 2

leandro.andrioli
leandro.andrioli

Reputation: 1047

You can use a custom validation attribute.

You can see bellow a solution that I used a few time ago:

/// <summary>
/// Provides conditional validation based on related property value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class RequiredIfAttribute : ValidationAttribute
{
#region Properties

/// <summary>
/// Gets or sets the other property name that will be used during validation.
/// </summary>
/// <value>
/// The other property name.
/// </value>
public string OtherProperty { get; private set; }

/// <summary>
/// Gets or sets the display name of the other property.
/// </summary>
/// <value>
/// The display name of the other property.
/// </value>
public string OtherPropertyDisplayName { get; set; }

/// <summary>
/// Gets or sets the other property value that will be relevant for validation.
/// </summary>
/// <value>
/// The other property value.
/// </value>
public object OtherPropertyValue { get; private set; }

/// <summary>
/// Gets or sets a value indicating whether other property's value should match or differ from provided other property's value (default is <c>false</c>).
/// </summary>
/// <value>
///   <c>true</c> if other property's value validation should be inverted; otherwise, <c>false</c>.
/// </value>
/// <remarks>
/// How this works
/// - true: validated property is required when other property doesn't equal provided value
/// - false: validated property is required when other property matches provided value
/// </remarks>
public bool IsInverted { get; set; }

/// <summary>
/// Gets a value that indicates whether the attribute requires validation context.
/// </summary>
/// <returns><c>true</c> if the attribute requires validation context; otherwise, <c>false</c>.</returns>
public override bool RequiresValidationContext
{
    get { return true; }
}

#endregion

#region Constructor

/// <summary>
/// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
/// </summary>
/// <param name="otherProperty">The other property.</param>
/// <param name="otherPropertyValue">The other property value.</param>
public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
    : base("'{0}' is required because '{1}' has a value {3}'{2}'.")
{
    this.OtherProperty = otherProperty;
    this.OtherPropertyValue = otherPropertyValue;
    this.IsInverted = false;
}

#endregion

/// <summary>
/// Applies formatting to an error message, based on the data field where the error occurred.
/// </summary>
/// <param name="name">The name to include in the formatted message.</param>
/// <returns>
/// An instance of the formatted error message.
/// </returns>
public override string FormatErrorMessage(string name)
{
    return string.Format(
        CultureInfo.CurrentCulture,
        base.ErrorMessageString,
        name,
        this.OtherPropertyDisplayName ?? this.OtherProperty,
        this.OtherPropertyValue,
        this.IsInverted ? "other than " : "of ");
}

/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>
/// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
    if (validationContext == null)
    {
        throw new ArgumentNullException("validationContext");
    }

    PropertyInfo otherProperty = validationContext.ObjectType.GetProperty(this.OtherProperty);
    if (otherProperty == null)
    {
        return new ValidationResult(
            string.Format(CultureInfo.CurrentCulture, "Could not find a property named '{0}'.", this.OtherProperty));
    }

    object otherValue = otherProperty.GetValue(validationContext.ObjectInstance);

    // check if this value is actually required and validate it
    if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
        this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
    {
        if (value == null)
        {
            return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
        }

        // additional check for strings so they're not empty
        string val = value as string;
        if (val != null && val.Trim().Length == 0)
        {
            return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
        }
    }

    return ValidationResult.Success;
}

Upvotes: 1

Related Questions