4

I am trying to automate the validation of my view models, I know I can just add a an attribute to specify my validation but there is an option to set up a factory to automate all that, I looked at: this answer and came up with this using simple injector 3.1:

public class CustomValidatorFactory:ValidatorFactoryBase
    {
        private readonly Container siContainer;
        public CustomValidatorFactory(Container siContainer)
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
            this.siContainer = siContainer;
            this.siContainer.Register(typeof(IValidator<>), assemblies);
        }
        public override IValidator CreateInstance(Type validatorType)
        {
            //var instances = siContainer.GetAllInstances(validatorType);
            var implementation = ((IServiceProvider)siContainer).GetService(validatorType);
            var validatorInstance = implementation != null ? (implementation as IValidator) : null;
            return validatorInstance;
        }
    }

Then the view model can be something like

public class Person {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
}

public class PersonValidator : AbstractValidator<Person> {
    public PersonValidator() {
        RuleFor(x => x.Id).NotNull();
        RuleFor(x => x.Name).Length(0, 10);
        RuleFor(x => x.Email).EmailAddress();
        RuleFor(x => x.Age).InclusiveBetween(18, 60);
    }
}

However the implementation variable is always null, I've also tried RegisterCollection but still have the same issue, seems like simple injector does not know how to resolve IValidator when the validator inherits from AbstractValidator(This is the class that implements IValidator)

Community
  • 1
  • 1
Daniel 1.618
  • 107
  • 1
  • 8
  • why are you registering your validators in the constructor of the factory? I'm suprised this code even works. I can't think of any reason why you would want that. You should place this code in your composition root of the application. – Ric .Net Nov 03 '15 at 23:17
  • Can you show a minimal reproducible example? How is CustomValidatorFactory registered and how is it called? What is actually supplied to the fsctory as parameter? Is the factory supplied with a typeof(Person) or typeof(IValidator)? – Steven Nov 04 '15 at 05:52
  • @Ric.Net, this CustomValidatorFactory can be considered to be part of the composition root, so making the registrations here seems fine by me. – Steven Nov 04 '15 at 05:58
  • @Steven This is how the factory was being registered: `FluentValidationModelValidatorProvider.Configure(provider => { provider.ValidatorFactory = new CustomValidatorFactory(container); });` – Daniel 1.618 Nov 04 '15 at 14:53

2 Answers2

7

Register the Fluent Validation Factory in Simple Injector like this:

public class ApplicationValidatorFactory : IValidatorFactory
{
    private readonly Container _container;

    /// <summary>The constructor of the factory.</summary>
    /// <param name="container">The Simple Injector Container</param>
    public ApplicationValidatorFactory(Container container)
    {
        _container = container;
    }

    /// <summary>Gets the validator for the specified type.</summary>
    public IValidator<T> GetValidator<T>()
    {
        return _container.GetInstance<IValidator<T>>();
    }

    /// <summary>Gets the validator for the specified type.</summary>
    public IValidator GetValidator(Type type)
    {
        var validator = typeof(IValidator<>).MakeGenericType(type);
        return (IValidator)_container.GetInstance(validator);
    }
}

And then in your Composition Root register it for ASP.NET MVC:

// Register the validators and factory
var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();   
container.Register<IValidatorFactory, ApplicationValidatorFactory>(Lifestyle.Singleton);
container.Register(typeof(IValidator<>), assemblies);

// Register Simple Injector validation factory in FV
FluentValidationModelValidatorProvider.Configure(provider => {
        provider.ValidatorFactory = new ApplicationValidatorFactory(container);
        provider.AddImplicitRequiredValidator = false;
    }
);

The FluentValidationModelValidatorProvider is found in the FluentValidation.MVC integration package. Remember to get the one for the version of MVC you are running.

UPDATE

You can register an empty validator for the object that does not have an validator like:

/// <summary>
/// Adds an unregistered type resolution for objects missing an IValidator.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
internal sealed class ValidateNothingDecorator<T> : AbstractValidator<T>
{
    // I do nothing :-)
}

// Add unregistered type resolution for objects missing an IValidator<T>
// This should be placed after the registration of IValidator<>
container.RegisterConditional(typeof(IValidator<>), typeof(ValidateNothingDecorator<>), Lifestyle.Singleton, context => !context.Handled);
Mrchief
  • 75,126
  • 20
  • 142
  • 189
janhartmann
  • 14,713
  • 15
  • 82
  • 138
  • 2
    Thanks for edit, @Steven - must have been some copy/waste :-) – janhartmann Nov 04 '15 at 07:35
  • 1
    this solution solved most of my issues, the only problem was that it was throwing an error when there was a view model with no validator so I just replaced this line: `return (IValidator)_container.GetInstance(validator);` with: `return ((IServiceProvider)_container).GetService(validator) as IValidator;` I think the only difference is that the GetService method will just return null which is ok for this scenario. – Daniel 1.618 Nov 04 '15 at 14:52
3

I would like to share my experience with integrating Simple Injector with FluentValidation here.

My first attempt is similar to what @janhartmann did, implement a concrete ValidatorFactoryBase which takes Simple Injector's Container as a dependency:

public class SimpleInjectorValidatorFactory : ValidatorFactoryBase
{
    private readonly Container _container;

    public SimpleInjectorValidatorFactory(Container container)
        => _container = container;

    public override IValidator CreateInstance(Type validatorType)
        => (IValidator)_container.GetInstance(validatorType);
}

public static class CompositionRoot
{
    public static void RegisterDependencies()
    {
        var container = new Container();
        FluentValidationModelValidatorProvider.Configure(
            provider => provider.ValidatorFactory = 
                new SimpleInjectorValidatorFactory(container));
    }
}

This works, however, I am using this factory under the context of an ASP.NET MVC project, and for view models that rely on the model binding magic that do not have IValidators registered for them, Simple Injector throws an ActivationException and crashes the application.

My second attempt is of course to put a try-catch block around GetInstance:

public class SimpleInjectorValidatorFactory : ValidatorFactoryBase
{
    private readonly Container _container;

    public SimpleInjectorValidatorFactory(Container container)
        => _container = container;

    public override IValidator CreateInstance(Type validatorType)
    {
        try
        {
            object validator = _container.GetInstance(validatorType);
            return (IValidator)validator;
        }
        catch (ActivationException)
        {
            // FluentValidation will handle null properly
            return null;
        }
    }
}

But then I am not satisfied with the fact that the try-catch will obviously slow down the resolution of validators so I look around SO to find the API that makes the Container return null instead of throwing an exception. This turns out to be possible because Container implements IServiceProvider explicitly, and IServiceProvider will return null if the type is not registered.

My third and final attempt, and since this validator factory no longer depends on Simple Injector, I have renamed it to ServiceProviderValidatorFactory:

public class ServiceProviderValidatorFactory : ValidatorFactoryBase
{
    private readonly IServiceProvider _serviceProvider;

    public ServiceProviderValidatorFactory(IServiceProvider serviceProvider)
        => _serviceProvider = serviceProvider;

    public override IValidator CreateInstance(Type validatorType)
        => (IValidator)_serviceProvider.GetService(validatorType);
}

public static class CompositionRoot
{
    public static void RegisterDependencies()
    {
        var container = new Container();
        FluentValidationModelValidatorProvider.Configure(
            provider => provider.ValidatorFactory = 
                new ServiceProviderValidatorFactory(container));
    }
}

This works and completely decoupled the validator factory with Simple Injector as an additional benefit.

Community
  • 1
  • 1
rexcfnghk
  • 14,435
  • 1
  • 30
  • 57
  • Nice! I took this even one step further and made a generic SimpleInjectorServiceProvider class and registered it so I could use it in factories of different types and I don't have to new up the ServiceProvider class. – Louise Eggleton Oct 08 '20 at 19:55