Search code examples
asp.netasp.net-mvcvalidationvalidationattribute

Custom ValidationAttribute: How to check for duplicate value, ignoring the object being edited


I'm trying to write a custom ValidationAttribute that verifies no records already exist with the same value.

The problem is that if the user is editing an existing record, then my code will find a record that matches the same value. It will find the one that is being edited if the user has not changed that value.

So I thought I could compare the value to the value in ValidationContext.ObjectInstance to detect when it hasn't changed, something like this:

public class UrlTagValidationAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        string tag = value as string;
        if (string.IsNullOrWhiteSpace(tag))
            return new ValidationResult("URL Tag is required.");

        // We allow a valid that is equal to the original value
        if (context.ObjectInstance is TrainerModel model && model.Tag == tag)
            return ValidationResult.Success;

        // Cannot be an existing tag
        using (var dbContext = new OnBoard101Entities())
        {
            if (!dbContext.TrainerDetails.Any(td => td.Tag == tag))
                return ValidationResult.Success;
        }

        return new ValidationResult("This URL Tag is not available. Please enter a different one.");
    }
}

But this doesn't work. I find that the value in ValidationContext.ObjectInstance often matches the value entered even when I'm creating a new record.

I'm having a hard time finding good and current documentation on the current usage of ValidationContext. Can someone suggest a way to check if any records exist that match the entered value BUT allowing it when a record is being edited and the value of this field has not changed?


Solution

  • The item which is currently being edited most likely has some kind of property to identify it (look it up in the database). Therefore, you need to get that property so you can exclude that when you are searching the database for duplicate tags. Here is how to do that in your custom validation class. I am making the assumption the identifier is named TrainerId:

    public class UrlTagValidationAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext context)
        {
            string tag = value as string;
            if(string.IsNullOrWhiteSpace(tag))
                return new ValidationResult("URL Tag is required.");
    
            var currentTrainer = validationContext.ObjectInstance 
                                     as TrainerModel;
            if (currentTrainer == null)
            {
                // What do you want to do? You cannot always return an error
                // because another type could be using this custom validation.
                // Or you can return an error. Depends on your requirements and 
                // and usage.
            }
            using(var dbContext = new OnBoard101Entities())
            {
                if(dbContext.TrainerDetails.Any(td => td.Tag == tag && td.TrainerId != 
                                                currentTrainer.TrainerId))
                {
                    return new ValidationResult("This URL Tag is not available. Please enter a different one.");
                }
            }
    
            return ValidationResult.Success;
        }
    }