在具有依赖项注入的.NET Core中使用FluentValidation

 收藏

我有一个按以下方式排列的.NET Core Web Api应用程序-

  1. 注入业务服务的控制器层
  2. 注入工作单元的业务服务(与数据库交互)
  3. 商业服务可能还会调用FluentValidation类
  4. FluentValidation将注入工作单元以执行数据库检查(存在等)。

因此,所有这些都是一个示例。如果要在系统中创建用户,则在“ UsersController”内部有一个称为“ PostUser”的路由/方法。 “ UsersController”注入“ UserService”。 “ UserService”具有一种称为“ CreateUser”的方法。因此,在控制器的“ PostUser”方法内部,它看起来像这样-

var user = _userService.CreateUser(user);

现在,在“ CreateUser”方法中,它看起来像这样-

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

因此,通过依赖注入将UnitOfWork传递到UserService中,然后传递给FluentValidation类“ UserValidation”,以便验证类可以执行数据库检查。我还将一个枚举传递给UserValidation类,以指定验证是否打算用于Update或Create。

我尝试验证的User对象将具有“ Role”和“ Company”之类的属性,并且每个对象都有单独的验证类(RoleValidation和CompanyValidation)。这两个验证类都将传入UnitOfWork,并且无论这是创建还是更新。

这是我的UserValidation类的示例-

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));

     }
}

现在了解了所有这些,我想为我的“ UserService”类创建单元测试。但是我相信,要正确执行此操作,在某些情况下,我需要模拟FluentValidation类,正如您在“ UserService” CreateUser方法中看到的那样,我为Validation实例化了具体的类。因此,为了做到这一点,我必须为我的每个fluentvalidation类创建一个接口,并将它们注入使用它们的业务服务中。所以我在Startup.cs文件中做了以下操作-

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

因此,现在执行此操作之后,我可以将IValidator注入我的UserService构造函数中并使用它,而不是在UserService方法内部实例化Concrete类。

因此,这给我带来了以下问题。

  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.

任何帮助/建议,将不胜感激。我真的被这个问题困扰。如果有人需要进一步澄清我面临的问题,请不要犹豫。

谢谢

回复
  • 时间在怂恿 回复

    我正面临类似的问题。但是你帮助了我。

    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.

    回答您的问题:

    问题1.我想我指出了上面的一个错误。否则对我来说很好。无需创建包装器即可对验证进行单元测试,您可以像下面的示例一样简单地执行此操作:

     [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.

    问题3:我想我已经回答了一个。

    问题4:我将尝试创建泛型依赖项注入,或将验证器数组注入某种工厂中,这些工厂将根据类型进行解析。

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

    哪个将根据类型解析我需要的验证器。

    希望这是有道理的。

    更新

    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

    例如: 代替这个:     ValidationResultvalidationResult =等待验证。ValidateAsync(user);

    你可以这样做:

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

    同样,您也可以解决依赖关系并注入诸如此类的必要验证器(我正在按请求类型进行解析,如果需要,可以使用字符串键):

      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;
                });
    

    这是用法示例:

     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