John Dorean
John Dorean

Reputation: 3874

Custom validation method that accesses other model properties

I'm attempting to create a custom validation method for one of my entities, so I've created a class that inherits from ValidationAttribute:

public class OneWheelchairPerTrainAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        // This is where I need to access the other entity property
    }
}

What I'm struggling with is how I access the other properties on the entity. This is my entity:

public class Ticket
{
    public int Id { get; set; }

    [Required]
    public int TimetableId { get; set; }

    [Required]
    public bool Wheelchair { get; set; }

    public virtual Timetable Timetable { get; set; }
}

The validation annotation I'm writing will be applied to the Wheelchair property, and I need to access the TimetableId property from within my validation method.

Upvotes: 1

Views: 3711

Answers (2)

Nick N.
Nick N.

Reputation: 13578

Another (in my opinion, better) way to validate multiple properties is by doing it on class level.

This not the exact same situation as in your answer, but it still involves multiple property validation.

Imagine you want to allow a wheelchair to be an id or a new object, but you still want to allow only one:

An example of my ExactlyOneRequired attribute:

[AttributeUsage(AttributeTargets.Class)]
public class ExactlyOneRequiredAttribute : ValidationAttribute
{
    public string FirstPropertyName { get; set; }
    public string SecondPropertyName { get; set; }

    //Constructor to take in the property names that are supposed to be checked
    public ExactlyOneRequiredAttribute(string firstPropertyName, string secondPropertyName)
    {
        FirstPropertyName = firstPropertyName;
        SecondPropertyName = secondPropertyName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value == null) 
           return new ValidationResult("Object must have a value;");

        var neededProperties = validationContext.ObjectType.GetProperties().Where(propertyInfo => propertyInfo.Name == FirstPropertyName || propertyInfo.Name == SecondPropertyName).Take(2).ToArray();
        var value1 = neededProperties[0].GetValue(value);
        var value2 = neededProperties[1].GetValue(value);

        if (value1 == null | value2 == null)
            return ValidationResult.Success;

        return FailedValidationResult();
    }

    public override string FormatErrorMessage(string name) => $"One of the fields: '{FirstPropertyName} or {SecondPropertyName}' is required, it is not allowed to set both.";

    private ValidationResult FailedValidationResult() => new ValidationResult(FormatErrorMessage(FirstPropertyName), new List<string> {FirstPropertyName, SecondPropertyName});
}

Usage:

[ExactlyOneRequired(nameof(WheelChairId), nameof(WheelChair))]
public class Train
{
    public int? WheelChairId { get; set; }

    public WheelChair WheelChair { get; set; }
}

You can of course expect as many properties as you want and make it as generic as you want. My point is, instead of string checking on property name within the attribute, injecting the property names is the cleaner way to go,

Upvotes: 6

wooters
wooters

Reputation: 997

You can use the IsValid overload to pass in a ValidationContext, like this:

public class OneWheelchairPerTrainAttribute : ValidationAttribute
{
  public override bool IsValid(object value, ValidationContext context)
  {
    Object instance = context.ObjectInstance;
    Type type = instance.GetType();
    // Here is your timetableId
    Object timeTableId = type.GetProperty("TimetableId ").GetValue(instance, null);

    //Do validation ...
   }
}

Upvotes: 4

Related Questions