0

I have a question about custom validator allowing multiple attribute I need to display a modal based on DOB. I'm having problems adding data-val properties in AddValidation method is only displaying the properties of the first attribute.

[AgeRange("ModalA", 0, 64, ErrorMessage = "Modal A Error")]
[AgeRange("ModalB", 65, 80, ErrorMessage = "Modal B Error")]
public override DateTime? DateOfBirth { get; set; }
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = true, Inherited = false)]
public class AgeRangeAttribute : ValidationAttribute, IClientModelValidator
{
    public AgeValidationProperties ValidationProps { get; set; }
 
    public AgeRangeAttribute(string id, int yearMinimum, int yearMaximum)
    {
        //Init
        ValidationProps = new AgeValidationProperties()
        {
            YearMinimum = yearMinimum,
            YearMaximum = yearMaximum,
            Id = id
        };
 
    }
 
    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        //Logic for Valid
    }
   
    public void AddValidation(ClientModelValidationContext context)
    {
//UPDATE!!!!
        var attributes = context.ModelMetadata.ContainerMetadata
                                            .ModelType.GetProperty(context.ModelMetadata.PropertyName)
                                            .GetCustomAttributes(typeof(AgeRangeAttribute), false)
                                            .Cast<AgeRangeAttribute>();

            var props = attributes.Select(x => x.ValidationProps).ToList();
            var messages = attributes.Select(x => x.ErrorMessage).ToList();

            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-agerange", JsonConvert.SerializeObject(messages));

            MergeAttribute(context.Attributes, "data-val-agerange-props", JsonConvert.SerializeObject(props));
    }
}
 
public class AgeValidationProperties
{
  public int YearMinimum { get; set; }
  public int YearMaximum { get; set; }
  public string Id { get; set; }
}

I want to validate DOB property based on the rules added in the DataAnnotations decorator if age is lower than 64 years old in client will display ModalA and if age is between 65 and 80 will display ModalB and greater than 80 is a valid date.

HTML generated

1 Answer 1

1

You can return error message directly in AgeRangeAttribute.

I did a simple test, you can refer to it:

AgeRangeAttribute :

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = true, Inherited = false)]
    public class AgeRangeAttribute : ValidationAttribute, IClientModelValidator
    {
        public AgeValidationProperties ValidationProps { get; set; }

        public int Year { get; }

        public AgeRangeAttribute(string id, int yearMinimum, int yearMaximum)
        {
            //Init
            ValidationProps = new AgeValidationProperties()
            {
                YearMinimum = yearMinimum,
                YearMaximum = yearMaximum,
                Id = id
            };

        }

        protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
        {
            if ((int)value < ValidationProps.YearMaximum) {
                return new ValidationResult(GetErrorMessage());
            }
            
            return ValidationResult.Success;
            //Logic for Valid
        }

        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-agerange", GetErrorMessage());

            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-agerange-props",year);
        }

        public string GetErrorMessage() 
        {
            if (ValidationProps.Id == "ModalA")
            {
                return "Your age is not valid because is lower than 64 years old";
            }
            else 
            {
                return "Your age is not valid because is between 65 and 80 years old.";
            }
        }

        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }

            attributes.Add(key, value);
            return true;
        }
    }
    public class AgeValidationProperties
    {
        public int YearMinimum { get; set; }
        public int YearMaximum { get; set; }
        public string Id { get; set; }
    }

Attributes that need to be validated:

[AgeRange("ModalA", 0, 64)]
[AgeRange("ModalB", 65, 80)]
public int Age { get; set; }

View:

<div class="row">
    <div class="col-md-4">
        <form method="post" asp-action="Index">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="@Model.Age" class="control-label"></label>
                <input asp-for="@Model.Age" class="form-control" />
                <span asp-validation-for="@Model.Age" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Submit" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
}

Result:

enter image description here

enter image description here

Hope this can help you.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks Chen I tested but having an error because of Year property not able to find in the code where do you assigned. public int Year { get; }
Hi @smashraid, I'm not using the Year property in my test, so I don't pass it in. If you need to use it, you can pass it in and do whatever you want with it. If you don't need it, you can just remove it.
I tested and is working as expected from server side the problem is client side data-val-agerange-props and data-val-agerange only display first data annotation [AgeRange("ModalA", 0, 64)] check this link ibb.co/MnNKtjM
Thank you very much @chen I updated the post with the final answer related to client side.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.