asp.net-mvcasp.net-mvc-5data-annotationsasp.net-mvc-5.2validationattribute

How to format error message from a custom validator


I have created a custom validator this way:

public class IntArrayRequiredAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if ((!(value is int[] array) || !array.Any() || array.Any(item => item == 0)))
            return new ValidationResult(this.ErrorMessage);

        return ValidationResult.Success;
    }
}

and apply it to a model property:

    [IntArrayRequiredAttribute(ErrorMessage = "You must select {0}.")]
    [Display(Name = "Rol")]
    public int[] Roles { get; set; }

Well, when the validation fails, this error is shown:

"You must select {0}."

How can I return the error message so that {0} is replaced by the display name of the field automatically, such as the built-in validators?

Expected result should be "You must select Rol."

EDIT:

By Seeing ValidationAttribute source code, I read:

    public ValidationResult GetValidationResult(object value, ValidationContext validationContext) {
        if (validationContext == null) {
            throw new ArgumentNullException("validationContext");
        }

        ValidationResult result = this.IsValid(value, validationContext);

        // If validation fails, we want to ensure we have a ValidationResult that guarantees it has an ErrorMessage
        if (result != null) {
            bool hasErrorMessage = (result != null) ? !string.IsNullOrEmpty(result.ErrorMessage) : false;
            if (!hasErrorMessage) {
                string errorMessage = this.FormatErrorMessage(validationContext.DisplayName);
                result = new ValidationResult(errorMessage, result.MemberNames);
            }
        }

        return result;
    }

I saw that it calls my overridden IsValid method and it formats the message. Why isn't it formatting in my case?

If I use the IsValid overload, it formats correctly, however, I need to use this method because I need validationContext for other validation purpose.


Solution

  • I don't think the reference source match the real code, as reflection revеаls:

    public ValidationResult GetValidationResult(object value, ValidationContext validationContext)
    {
        if (validationContext == null)
        {
            throw new ArgumentNullException("validationContext");
        }
        ValidationResult validationResult = IsValid(value, validationContext);
        if (validationResult != null && (validationResult == null || string.IsNullOrEmpty(validationResult.ErrorMessage)))
        {
            string errorMessage = FormatErrorMessage(validationContext.DisplayName);
            validationResult = new ValidationResult(errorMessage, validationResult.MemberNames);
        }
        return validationResult;
    }
    

    So if you want to fit everything into the single overload

    protected override ValidationResult IsValid(object value, ValidationContext validationContext) {...}
    

    you could let the base class do the ErrorMessage formatting:

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if ((!(value is int[] array) || !array.Any() || array.Any(item => item == 0)))
        {
           return new ValidationResult(null);
        }
    
        return ValidationResult.Success;
    }
    

    or you could do the formatting yourself:

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if ((!(value is int[] array) || !array.Any() || array.Any(item => item == 0)))
        {
            string errorMessage = FormatErrorMessage(validationContext.DisplayName);
            return new ValidationResult(errorMessage);
        }
    
        return ValidationResult.Success;
    }