Custom Validator Attributes - using other proprty values in error message

Viewed 488

I have a custom attribute, that relies on other model properties to function. the attribute takes property name arguments to use to get values out of the associated model to perform validation. I attach it to a field like this

AViewModel{
    [DateBetweenAges(minProperty:"MinAge", maxProperty:"MaxAge", ErrorMessage = "Your age is not between {1} and {2}")]
    public DateTime? DoB { get; set; }

        public int MinAge { get; set; }
        public int MaxAge { get; set; }
}

I have hooked this up using a Adapter, and an AdaptorProvider to offer client validation by following the example here Defining Custom Client Validation Rules in Asp.net Core MVC but I am having trouble getting the error message to say the correct thing which would be for example: 'Your age is not between 20 and 30'

20 and 30 being the values attached to the MinAge and MaxAge properties on the ViewModel (set when the page is loaded)

in the GetErrorMessage(ModelValidationContextBase validationContext) method in the Adapter, I seem to have no way of getting to the values in the ViewModel at all..

in the IsValid method of the attribute itself I get these values using reflection by doing

var maxprop = validationContext.ObjectType.GetProperty(MaxProperty);
var maxPropVal = maxProperty.GetValue(validationContext.ObjectInstance, null);

but these seems to be no object instance in the adapter, although inspecting does get me to the ViewData which does have it in..

public class DateBetweenAgesAttributeAdapter : AttributeAdapterBase<DateBetweenAgesAttribute>
{
    private readonly DateBetweenAgesAttribute _attribute;

    public DateBetweenAgesAttributeAdapter(DateBetweenAgesAttribute attribute, IStringLocalizer localizer) : base(attribute, localizer)
    {
        _attribute = attribute;
    }

    public override void AddValidation(ClientModelValidationContext context)
    {
        MergeAttribute(context.Attributes, "data-val", "true");
        MergeAttribute(context.Attributes, "data-val-datebetweenages", GetErrorMessage(context));
        MergeAttribute(context.Attributes, "data-val-datebetweenages-min", _attribute.MinProperty);
        MergeAttribute(context.Attributes, "data-val-datebetweenages-max", _attribute.MaxProperty);
    }

    public override string GetErrorMessage(ModelValidationContextBase validationContext)
    {
return GetErrorMessage(validationContext.ModelMetadata,
            validationContext.ModelMetadata.GetDisplayName(),
            //would like to pass the actual minvalue in here,
            //would like to pass the actual maxvalue in here);
    }
..

how do I go about that ??

I realise I could use the [Remote] attribute for this, which maybe I will end up doing, but that seems to mean duplicating stuff..

1 Answers

I got this working by putting the attribute's properties into the error message on the Attribute class instead of the Adapter, for example:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class DateBetweenAgesAttribute : ValidationAttribute {
    public int MinProperty { get; set; }
    public int MaxProperty { get; set; }

    public DateBetweenAgesAttribute(int min, int max) : base() {
        MinProperty = min;
        MaxProperty = max;
        ErrorMessage = $"Your age is not between {MinProperty} and {MaxProperty}";
    }
}

then you don't need to do anything in DateBetweenAgesAttributeAdapter.GetErrorMessage, it just works as is.

Related