Search code examples
c#validationdependency-injectionsimple-injectorfluentvalidation

FluentValidation using validator on wrong viewmodel


I'm using FluentValidation for the first time. I had some basic validation working, but then I realized I would need to do some database retrieval for some more complicated validation. That required doing Dependency Injection so I could use the database service, and that leads me to my current state: Stuck. I cannot get this to work.

To simplify things, I'll pretend that my application is dealing with sports leagues and teams, because I figure that's an easier mental model than contracts, invoices, funding sources, vendors, and subcontractors. :-)

So, suppose I have a viewmodel for a sports league. Within that viewmodel there is a collection of viewmodels for the teams that are in that league.

I have a screen to edit a league. That same screen allows changes to some of the information about the teams that are in that league.

LeagueViewModel

The viewmodel for the league contains a List of viewmodels for the teams.

[FluentValidation.Attributes.Validator(typeof(LeagueValidator))]
public class LeagueViewModel
{
    public string LeagueName { get; set; }
    public DateTime SeasonBeginDate { get; set; }
    public DateTime SeasonEndDate { get; set; }

    public List<TeamViewModel> TeamViewModels { get; set; }
}

I've created a validator for the LeagueViewModel. Unfortunately, when I edit the league and click the submit button, I get this error message:

InvalidCastException: Unable to cast object of type 'TeamViewModel' to type 'LeagueViewModel'. at FluentValidation.ValidationContext.ToGenericT

Apparently it is attempting to validate the TeamViewModel using the LeagueValidator.

I have gone through many variations trying to figure out how to get this to work. Here's what I have at the moment.

Validator

public class LeagueValidator : AbstractValidator<LeagueViewModel>
{
    private readonly ILeagueService _leagueService;

    public LeagueValidator(ILeagueService leagueService)
    {
        _leagueService = leagueService;

        RuleFor(x => x.SeasonEndDate)
            .NotNull()
            .GreaterThan(x => x.SeasonBeginDate)
            .WithMessage("Season End Date must be later than Season Begin Date.");
    }
}

(The LeagueService bit is in there because in the real code it needs to check against some database values, which it uses the service to retrieve.)

Note that the LeagueValidator doesn't have any validation rules for any fields in the List of TeamViewModels.

League Validator Factory

public class LeagueValidatorFactory : ValidatorFactoryBase
{
    private readonly Container _container;

    public LeagueValidatorFactory(Container container)
    {
        _container = container;
    }

    public override IValidator CreateInstance(Type validatorType)
    {
        return _container.GetInstance<LeagueValidator>();
    }
}

Dependency Injector

We're using SimpleInjector for DI. As part of that existing setup, it is calling a method to register the services. Within that method I've added a call to this:

private static void RegisterValidators(Container container)
{
    DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

    var leagueValidatorProvider =
        new FluentValidationModelValidatorProvider(new LeagueValidatorFactory(container));
    leagueValidatorProvider.AddImplicitRequiredValidator = false;
    ModelValidatorProviders.Providers.Add(leagueValidatorProvider);

    container.Register<LeagueValidator>();
}

Questions

  1. How do I get this to work properly?
  2. Why is it trying to use the LeagueValidator to validate the TeamViewModel?
  3. Do I need to have a separate validator and validator factory for every view model?
  4. Even those that don't have any validation rules?
  5. How do I tell it which validator to use for which viewmodel?

I figure I must be misunderstanding something basic.

Edit

Steven's response below got me pointed in the right direction! After I made his suggested changes, I encountered another error. Once I got that fixed, it works! Here are the changes I made to get the code above working properly.

LeagueViewModel

I removed this line, as it isn't necessary.

[FluentValidation.Attributes.Validator(typeof(LeagueValidator))]

LeagueValidatorFactory

I renamed it to "ValidatorFactory", because it turns out there will only be one validator factory, regardless of how many validators I create. I then changed the CreateInstance method to this:

    public override IValidator CreateInstance(Type validatorType)
    {
        if (_container.GetRegistration(validatorType) == null)
        {
            return null;
        }

        return (IValidator)_container.GetInstance(validatorType);
    }

This no longer explicitly specifies the type of validator to get (which is why only one factory will be needed). To determine whether a validator for the given type is available, it does a call to GetRegistration, returning null if none is found.

This was important! For every viewmodel, it is going to try to find a validator. Without this null check, an InvalidCastException gets thrown.

Dependency Injector

Following Steven's suggestion, I replaced the container.Register line with this:

container.Register(typeof(IValidator<>), new[] { typeof(SimpleInjectorInitializer).Assembly });

That avoids the need to explicitly list each validator every time a new one is added.

And now it all works! Thanks very much for your help, Steven!


Solution

  • I'm unfamiliar with FluentValidation, but it seems your LeagueValidatorFactory is requesting the wrong type from the container, considering it is supplied with the type to validate.

    Because of this, I'd expect your validation factory to look something like this:

    public class LeagueValidatorFactory : ValidatorFactoryBase
    {
        private readonly Container _container;
    
        public LeagueValidatorFactory(Container container) =>
            _container = container;
    
        public override IValidator CreateInstance(Type validatorType) =>
            (IValidator)_container.GetInstance(validatorType);
    }
    

    What I can see from the FluentValidator source code, is that the validatorType is a closed-generic version of the IValidator<T> type, with the T being the actual type being validated. This means, that you will have to register the validators by their IValidator<T> interface. For instance:

    container.Register<IValidator<LeagueViewModel>, LeagueValidator>();
    

    This Configuration as Code (or explicit-register) model, where you register every validator explicitly using a line of code, might work fine if you have just a few validators, but this will typically result in a Composition Root that has to be updated frequently.

    A better model, therefore, is to use Auto-Registration, where you register all IValidator<T> implementations, using reflection. Fortunately, you don't have to implement this yourself; Simple Injector has your back:

    var validatorAssemblies = new[] { typeof(LeagueValidator).Assembly };
    container.Register(typeof(IValidator<>), validatorAssemblies);
    

    This makes sure that you never have to change your Composition Root when you just added a new validator (in that particular assembly).

    With this setup, I see no reason why you should mark your view model with the FluentValidation.Attributes.ValidatorAttribute. If you can, please remove it, as it only causes untight coupling between your view model and the validator.