Search code examples
razor-pagesfluentvalidationmodelstate

Copying FluentValidation Errors to the ModelState for a complex property?


When I follow the FluentValidation docs and copy the FluentValidationm error to the ModelState dictionary, only simple properties will cause asp-validation-for attributes to work. When I use a complex property it will not work unless I prepend the class name to the ModelState key.

.NET 7, FluentValidation 11.4.0, RazorPages.

HTML

<form method="post">
    <div asp-validation-summary="All"></div>

    <input type="text" asp-for="Sample.TestValue" />
    <!-- Wont work unless prepend "Sample" to ModelState dictionary error key -->
    <span asp-validation-for="Sample.TestValue"></span>
    <button type="submit">Do it</button>
</form>

CodeBehind

namespace ValForTest.Pages;

using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

public class SampleValidator : AbstractValidator<Sample>
{
    public SampleValidator()
    {
        RuleFor(x => x.TestValue)
            .MaximumLength(1);
    }
}

public class Sample
{
    public string? TestValue { get; set; }
}

public class IndexModel : PageModel
{
    [BindProperty]
    public Sample Sample  { get; set; }  

    public void OnPost()
    {
        var validator = new SampleValidator();
        var result = validator.Validate(this.Sample);

        foreach (var error in result.Errors) 
        {
            this.ModelState.AddModelError(error.PropertyName, error.ErrorMessage);

            // This works!!! Code smell though. Better way to do this??
            // this.ModelState.AddModelError($"{nameof(Sample)}.{error.PropertyName}", error.ErrorMessage);
        }
    }    

    public void OnGet() { }
}

Result:

enter image description here

asp-validation-summary works, asp-validation-for does not.

However, if I uncomment my // this works line where I add the "fully qualified" property name which includes the complex class name, then it will show the asp-validation-for span:

enter image description here

How can I tell FluentValidation to add the class name to the properties?


Solution

  • If you can, I suggest using the FluentValidation.AspNetCore package since it has an AddToModelState extension method that takes a prefix which will solve your issue.

    Your OnPost() page handler then becomes:

        public void OnPost()
        {
            var validator = new SampleValidator();
            var result = validator.Validate(this.Sample);
            result.AddToModelState(ModelState, nameof(this.Sample);
        }    
    

    Although to replicate this functionality yourself would be simple as well. For reference, here is how it is done in the library:

        /// <summary>
        /// Stores the errors in a ValidationResult object to the specified modelstate dictionary.
        /// </summary>
        /// <param name="result">The validation result to store</param>
        /// <param name="modelState">The ModelStateDictionary to store the errors in.</param>
        /// <param name="prefix">An optional prefix. If omitted, the property names will be the keys. If specified, the prefix will be concatenated to the property name with a period. Eg "user.Name"</param>
        public static void AddToModelState(this ValidationResult result, ModelStateDictionary modelState, string prefix) {
            if (!result.IsValid) {
                foreach (var error in result.Errors) {
                    string key = string.IsNullOrEmpty(prefix)
                        ? error.PropertyName
                        : string.IsNullOrEmpty(error.PropertyName)
                            ? prefix
                            : prefix + "." + error.PropertyName;
                    modelState.AddModelError(key, error.ErrorMessage);
                }
            }
        }