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.
<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>
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:
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:
How can I tell FluentValidation to add the class name to the properties?
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);
}
}
}