question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Custom Validator not working when binding on multiple complex objects

See original GitHub issue

System Details

  • FluentValidation version: 8.1.3
  • Web Framework version: ASP.NET Core 2.2 (Razor Pages)

Issue Description

I am using two separate objects in ASP.Net Core 2.2 Razor Pages to bind data on.

Object A:

public string Title { get; set; }
public string Message { get; set; }

Object B:

public IFormFile FileToUpload { get; set; }

Each one has separate validators. Object A uses built in validators for NotNull(). Object B uses a custom validator for which I am passing in a file validator service for my custom validation.

public class OptionalFileValidator : AbstractValidator<ObjectB>
    {
        public OptionalFileValidator(IFileValidate fileValidate)
        {
            RuleFor(file => file.FileToUpload).SetValidator(new CustomFileValidator(fileValidate));
        }
    }

The form that the user submits is very straightforward:

<form enctype="multipart/form-data">
        <div class="form-group">
            <input asp-for="ObjectA.Title" class="form-control" />
            <span asp-validation-for="ObjectA.Title" class="text-danger"></span>
        </div>
        <div class="form-group">
            <input asp-for="ObjectB.FileToUpload" type="file" accept=".png,.jpg,.bmp" /><br />
            <span asp-validation-for="ObjectB.FileToUpload" class="text-danger"></span>
        </div>
</form>

I tested two different scenarios:

  • Have a separate object for the IFormFile to bind on (i.e. Object B)
  • Include the IFormFile property into Object A

When the IFormFile exists in a separate object (Object B), model validation is not working correctly. The custom validator correctly identifies that the property failed model validation BUT its attaching the error to a modelstate key named “FileToUpload” instead of correctly appending it to “ObjectB.FileToUpload”.

image

If the IFormFile exists in the same object (Object A), this issue does not occur and custom validator is correctly appending the error to the modelstate key named “ObjectA.FileToUpload”.

Note that if I disable FluentValidation for ObjectB and manually run the validation in my controller and attach the error message to ObjectB.FileToUpload, everything works correctly. FluentValidation for some reason is not attaching the model error to the right instance.

My custom validator for reference:

public CustomFileValidator(IFileValidate fileValidate): base("{ErrorMessage}")
        {
            _fileValidate = fileValidate;
        }

        protected override async Task<bool> IsValidAsync(PropertyValidatorContext context, CancellationToken cancellation)
        {
            var fileToValidate = context.PropertyValue as IFormFile;          
            var (Valid, ErrorMessage) = await _fileValidate.ValidateFileAsync(fileToValidate);

            if (!Valid)
            {
                context.MessageFormatter.AppendArgument("ErrorMessage", ErrorMessage);
                return false;
            }

            return true;
        }

In my custom validator, the PropertyValidatorContext is correctly identifying the instance as being “ObjectB.FileToUpload” yet its not attaching the error to that property. Any reason it works this way?

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
JeremySkinnercommented, Feb 4, 2019

I’ve opened an issue on the asp.net core repo reporting this. https://github.com/aspnet/AspNetCore/issues/7238

1reaction
JeremySkinnercommented, Feb 4, 2019

Very strange. If you leave the form field blank and try and submit the form, the error message is added correctly to ModelState with the key ObjB.FileToValidate. The problem only occurs if you actually supply a file to upload. The same thing happens if you remove ObjA completely, so it’s not related to having multiple models on the page.

However, this doesn’t appear to be an issue with FluentValidation - ASP.NET itself is failing to generate the “ObjB” prefix in this case - it’s never being passed to FluentValidation. I’d say this is a bug in ASP.NET Core. It only happens for models that contain a reference to IFormFile, and only when a file is uploaded.

For now you can work around it by explicitly adding Name to your BindProperty:

[BindProperty(Name = "ObjB")]
public ObjectB ObjB { get; set; }

Quite why this is happening I’m uncertain…debugging the model binding process is pretty difficult and I can’t see anything obvious.

Read more comments on GitHub >

github_iconTop Results From Across the Web

spring - Validation does not work on nested objects
I'm trying to simplify my code by binding request parameters directly to User Entity instead of copying value field by field, but I...
Read more >
Built-In, Nested, Custom Validators with FluentValidation
In this article we are going to learn more about different Validators with FluentValidation, that we can use to protect our app from...
Read more >
Spring MVC Custom Validation
This tutorial focuses on Spring MVC. Our article entitled Validation in Spring Boot describes how to create custom validations in Spring Boot.
Read more >
The best way to implement custom validators - Angular ...
Learning best practices on how to build your custom validator in Angular by reverse engineering the built-in Angular validators.
Read more >
Angular Custom Form Validators: Complete Guide
All about custom form validators, including synchronous and asynchronous, field-level, form-level, for both template-driven and reactive forms.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found