Search code examples
c#asp.netasp.net-mvcfluentvalidation

FluentValidation not working on collection of outer model objects


I am having trouble getting FluentValidation to work with a collection of objects. My controller POST action takes in an IEnumerable of objects like below. When I post to an action that takes a single EventInputDto, with an incorrectly formatted Url property, my validation occurs successfully. When I post to a collection of EventInputDto, it does not work and does no validation.

If I use regular MVC Attributes (i.e. required / email), they work with collections as well as single objects. How do I get this to work with FluentValidation? I am not working with inner collections so I'm not sure why it does not work as intended.

public async Task<IActionResult> CreateEventCollection([FromBody] IEnumerable<EventInputDto> events)
{
    if (!ModelState.IsValid)
    {
    return UnprocessableEntity(ModelState); //does not work
    }
}

My validators are setup using generics because I am using separate models for inputs and updates.

public class EventManipulationValidator<T> : AbstractValidator<T> where T : EventManipulationDto
    {
        public EventManipulationValidator()
        {
            RuleFor(manipulationDto => manipulationDto.Title).NotNull().WithMessage("Title cannot be blank")
              .Length(1, 50);

            RuleFor(manipulationDto => manipulationDto.Message).NotNull().WithMessage("Message cannot be blank")
                  .Length(1, 1000);

            RuleFor(manipulationDto => manipulationDto.ScheduledTime).NotNull().WithMessage("Scheduled Time cannot be blank");

            RuleFor(inputDto => inputDto.Url).Matches(@"https://.*windows\.net.*").WithMessage("The url must be valid and stored on Azure");
        }
    }

As my CreateEventCollection action takes in an IEnumerable of EventInputDto, my validator for EventInputDto is setup as below:

public class EventInputValidator : EventManipulationValidator<EventInputDto>
    {
        public EventInputValidator()
        {
            //all property validators are inherited from EventManipulationValidator
        }
    }
    public class EventInputCollectionValidator : AbstractValidator<IEnumerable<EventInputDto>>
    {
        public EventInputCollectionValidator()
        {
            RuleForEach(p => p).SetValidator(new EventManipulationValidator<EventInputDto>());
        }
    }

Below are my models for reference:

EventManipulationDto

public abstract class EventManipulationDto
    {
        public string Title { get; set; }
        public string Message { get; set; }
        public string Url { get; set; }
        public DateTime? ScheduledTime { get; set; }
    }

EventInputDto

 public class EventInputDto : EventManipulationDto
  {
     //all properties inherited from base class
   }

Solution

  • After going through the list of open/closed issues on the project GitHub, it seems that not all of my approach is required. There is no need for my `EventInputCollectionValidator. FluentValidation no longer requires explicitly defining an IEnumerable validator like I defined above.

    It's enough to define a base AbstractValidator or as in my case an inherited validator from a parent class.

    The only change needed to get it to work was in my startup.cs when registering fluentvalidation. I needed to explicitly add ImplicitlyValidateChildProperties = true. Didn't realize this was required as I thought this was for validating child property collections and not the parent collection objects. Works perfectly now.

    .AddFluentValidation(fv => {
                        fv.RunDefaultMvcValidationAfterFluentValidationExecutes = true;
                        fv.RegisterValidatorsFromAssemblyContaining<Startup>();
                        fv.ImplicitlyValidateChildProperties = true;
                    });