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

时间:2019-05-18 20:22:40

标签: dependency-injection .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类,以指定验证是用于更新还是创建。

我要验证的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. 在您看来,我已经将项目结构化了,这是将依赖注入与FluentValidation一起使用并允许对服务方法进行单元测试以及FluentValidation类的单元测试的最佳方法吗?
  2. 是否有更好的方法将依赖注入与FluentValidation一起使用来完成所有这些操作,同时让FluentValidation类知道它是“ Create”还是“ Update”,而不是创建一个名为“ UserCreateValidation”的类”和“ UserUpdateValidation”或将变量“ DatabaseOperation”传递给Validator的构造函数?
  3. 在尝试设置FluentValidation DependencyInjection时附加到(2),我在传递“ DatabaseOperation”变量services.AddScoped<IValidator<User>>(x => new UserValidation(x.GetRequiredService<IUnitOfWork>(), <How to figure out if its a create or an update>));
  4. 时遇到了麻烦
  5. 最重要的是,我还必须在“ Startup.cs”文件中添加两行,以创建“ CompanyValidation”和“ RoleValidation”的作用域DependencyInjection,以便在“ UserValidation”内部以及无论是更新还是创建,这些验证类也将通过。

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

谢谢

1 个答案:

答案 0 :(得分:0)

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

我做了不同的事情/将会做不同的事情。可以使用RuleSet代替Create或Update,而可以使用RuleSets,具体取决于它将执行不同RuleSets的名称,这将使您可以在验证操作时标识操作:https://fluentvalidation.net/start#rulesets。此时,您不应注入任何依赖于运行时结果的内容,例如是否正在创建或更新。

回答您的问题:

问题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"));
    }

问题2:我只向验证器注入一个scopedFactory,让它自己解决其缺陷,而不是注入所需的一切。但是,您在new CompanyValidator(_unitOfWork, databaseOperation)内部正在做什么?在Validator中注入任何内容对我来说似乎很奇怪,因为您注入的并不是解决规则的真正内容。我不确定您的情况如何,但是如我所说,否则我将必须注入scopedFactory或Nested类。

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

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

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

哪个会根据类型确定我需要的验证器。

希望这很有道理。

更新

因此,在CreateMethod中,将RuleSet名称传递给validate方法,以便他解决是Create还是Update的问题。关于范围工厂https://csharp.hotexamples.com/examples/-/IServiceScopeFactory/-/php-iservicescopefactory-class-examples.html

例如: 代替这个:     ValidationResult validateResult =等待验证。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);
        }

然后将IServiceScopeFactory serviceScopeFactory注入到您的验证器中,这将有助于解决任何外部依赖项。您可以在此处找到示例:https://csharp.hotexamples.com/examples/-/IServiceScopeFactory/-/php-iservicescopefactory-class-examples.html