Using FluentValidation in .NET Core with Dependency Injection

16,074

I am facing a similar issue. However you helped me out.

What I did differently/Would do differently. instead of Create or Update, you can use RuleSets, depending on the name it will execute different RuleSets, this will allow you to identify the operation when you are validating it: https://fluentvalidation.net/start#rulesets. You should not be injecting anything that is dependen on the runtime result at this point such indication if it is create or update.

Answering your questions:

Question 1. I think I pointed one mistake above. Otherwise looks fine to me. It is not needed to create a wrapper to unit test your validation, you can simple do this like in this example:

 [Test]
    public void Should_have_error_when_val_is_zero()
    {
        validator = new TestModelValidator();
        TestModel testRequest = new TestModel();
        //populate with dummy data
        var result = validator.Validate(testRequest);
        Assert.That(result.Errors.Any(o => o.PropertyName== "ParentVal"));
    }

Question 2: I would inject just a single scopedFactory to the validator and let it resolve its depedencies himself, instead of injecting everything that it needs. However what are you doing inside new CompanyValidator(_unitOfWork, databaseOperation) ? It seems strange to me to inject anything in Validator since it is not really something you would inject that resolves the rule. I am not sure what is your case for that, but otherwise I would have, as I said, scopedFactory injected or a Nested class to do that.

Question 3: I think I answered that one already.

Question 4: I would try to create a generic dependency injection, or inject an Array of Validators into somekind of factory which would resolve based on type.

services.AddScoped(typeof(IValidationFactory<>), typeof(ValidationFactory<>));

Which would resolve which validator I need based on type.

Hope this makes sense.

UPDATE

So inside the CreateMethod pass the RuleSet name to the validate method for him to solve if it is a Create or Update. About scoped factory https://csharp.hotexamples.com/examples/-/IServiceScopeFactory/-/php-iservicescopefactory-class-examples.html

For example: Instead of this: ValidationResult validationResult = await validation.ValidateAsync(user);

You can do this:

validator.Validate(person, ruleSet: "Create");

As well you can resolve dependencies and inject necessary validator like this for example (I am resolving by request type you can use a string key if needed):

  services.AddSingleton<IValidator, Validator1>();
            services.AddSingleton<IValidator, Validator2>();
            services.AddSingleton<IValidator, Validator3>();

            services.AddScoped<Func<Type, IValidator>>(serviceProvider => typeKey =>
            {
                if (typeKey == typeof(Validator1))
                {
                    return serviceProvider.GetService<Validator1>();
                }
                if (typeKey == typeof(Validator2))
                {
                    return serviceProvider.GetService<Validator2>();
                }

                if (typeKey == typeof(Validator3))
                {
                    return serviceProvider.GetService<Validator3>();
                }

                return null;
            });

And this is usage example:

 public GenericValidator(Func<Type, IValidator> validatorFactory)
        {
            _validatorFactory = validatorFactory ?? throw new ArgumentNullException(nameof(validatorFactory));
        }


 public async Task<IEnumerable<string>> ValidateAsync<T, TK>(TK objectToValidate) where TK : class
        {
            var validator = _validatorFactory(typeof(T));

            if (validator == null)
            {
                throw new ValidationException($"Failed to get validator for type: {typeof(T)}");
            }

            var validationResult = await validator.ValidateAsync(objectToValidate);

            return validationResult.Errors.Select(x => x.ErrorMessage);
        }

And inject: IServiceScopeFactory serviceScopeFactory to your validator which will help resolve any external dependencies. You can find examples here: https://csharp.hotexamples.com/examples/-/IServiceScopeFactory/-/php-iservicescopefactory-class-examples.html

Share:
16,074
BryMan
Author by

BryMan

Updated on June 06, 2022

Comments

  • BryMan
    BryMan almost 2 years

    I have a .NET Core Web Api Application which is arranged in the following manner -

    1. Controller layer which injects Business Service
    2. Business Service which injects Unit Of Work (to interact with database)
    3. Business Service might also make a call to a FluentValidation class
    4. FluentValidation will inject the Unit Of Work to perform database checks (Exists, etc.)

    So having said all of that here is an example. If I want to create a User in the system I have a route/method called "PostUser" located inside of the "UsersController". The "UsersController" injects the "UserService". The "UserService" has a method called "CreateUser". So inside of the "PostUser" method of the controller it looks like this -

    var user = _userService.CreateUser(user);
    

    Now inside of the "CreateUser" method it looks like this -

    UserValidation validation = new UserValidation(UnitOfWork, DatabaseOperation.Create);
    ValidationResult validationResult = await validation.ValidateAsync(user);
    

    So the UnitOfWork is passed into the UserService via dependency injection and then passed along to the FluentValidation class "UserValidation" so the validation class can perform database checks. I also pass an enum into the UserValidation class to specify whether or not the validation is intended for an Update or a Create.

    The User object I am trying to validate will have properties such as "Role" and "Company" and I also have separate validation classes for each (RoleValidation and CompanyValidation). Both of these validation classes will also pass in a UnitOfWork and whether or not this is a create or an update.

    Here is an example of my UserValidation Class -

    public class UserValidation : AbstractValidator<UserDTO> 
    {
         private IUnitOfWork _unitOfWork;
         public UserValidation(IUnitOfWork unitOfWork, DatabaseOperation databaseOperation)
         {
             _unitOfWork = unitOfWork;
    
             if (databaseOperation == DatabaseOperation.Create)
             {
                  // Do Create specific validation
             }
    
             RuleFor(x => x.Company)
                .SetValidator(new CompanyValidator(_unitOfWork, databaseOperation));
    
         }
    }
    

    Now understanding all of this I wanted to create Unit Tests for my "UserService" class. But I believe in order to properly do this I would need to Mock out the FluentValidation class in some cases and as you can see in my "UserService" CreateUser method I instantiate the concrete class for my Validation. So in order to do this I would have to create an interface for each of my fluentvalidation classes and inject them into the business services that use them. So I did the following in my Startup.cs file -

    services.AddScoped<IValidator<User>>(x => new UserValidation(x.GetRequiredService<IUnitOfWork>()));
    

    So now after doing that I can inject the IValidator into my UserService Constructor and use that instead of instatiating a Concrete class inside of my UserService methods.

    So with this brings me to ask the following questions.

    1. In your opinion, the way I already have my project structured, is this the best way to use dependency injection with FluentValidation and allow for unit testing of the service method along with unit testing of the FluentValidation class?
    2. Is there a better way using dependency injection with FluentValidation to do all of this, and at the same time let the FluentValidation class know if it is a "Create" or an "Update", instead of creating one class called "UserCreateValidation" and "UserUpdateValidation" or passing in a variable "DatabaseOperation" to the constructor of the Validator?
    3. Appending to (2) when trying to setup the FluentValidation DependencyInjection I am having trouble passing in the "DatabaseOperation" variableservices.AddScoped<IValidator<User>>(x => new UserValidation(x.GetRequiredService<IUnitOfWork>(), <How to figure out if its a create or an update>));
    4. On Top of that I will have to also add two lines to the "Startup.cs" file to create the Scoped DependencyInjection of the "CompanyValidation" and the "RoleValidation" to be used inside of the "UserValidation" and both of those validation classes will also pass in whether or not it is an update or a create.

    Any help/suggestions would be appreciated. I am really stuck on this issue. If anyone needs anymore clarification on the issues I am facing please do not hesitate to ask.

    Thank You

  • BryMan
    BryMan almost 5 years
    Hi, thank you very much for your response. The main issue I am facing is that the company validator will be called inside of the user validator. I can add rulesets (I looked into that). So i can have a ruleset in the user validator for create and one for update. But the issue is how do i tell the company validator (which is inside of the user validator) which ruleset to use. The company validator will also have 2 rulesets (one for create and one for update). Basically I want the company validator to inherit whichever ruleset that the uservalidator got called with.
  • BryMan
    BryMan almost 5 years
    Also, would you mind explaining, or giving a brief example of your answer to (2). I am not familiar with using scopedfactory's. Thank You
  • BryMan
    BryMan almost 5 years
    Ok thanks for the detailed response. Maybe you misunderstood one of the questions. So you provided this example for using rulesets - validator.Validate(person, ruleSet: "Create"); But remember that the "validator" class also has a "CompanyValidator" in there also. How do I tell that "CompanyValidator" which ruleset to use (how to pass it in somehow)?
  • BryMan
    BryMan almost 5 years
    Actually I think I found my answer here: https://github.com/JeremySkinner/FluentValidation/issues/194‌​. Seems like if I do this validator.Validator(person, ruleSet: "Create") and I have another validator inside such as the CompanyValidator (and that company validator also has a "Create" ruleset) it will also get executed without having to pass anything in
  • vsarunov
    vsarunov almost 5 years
    Glad you did it! :)
  • Jan
    Jan almost 4 years
    Beware that with FluentValidation 9.0 this will fail on validator.ValidateAsync, the objectToValidate has to be wrapped into a ValidationContext as described here: docs.fluentvalidation.net/en/latest/…