通过依赖注入在 .NET Core 中使用 FluentValidation [英] Using FluentValidation in .NET Core with Dependency Injection

查看:51
本文介绍了通过依赖注入在 .NET Core 中使用 FluentValidation的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

说了这么多,这里只是一个例子.如果我想在系统中创建一个用户,我有一个名为PostUser"的路由/方法位于UsersController"内.UsersController"注入UserService".UserService"有一个名为CreateUser"的方法.所以在控制器的PostUser"方法内部,它看起来像这样 -

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

现在在CreateUser"方法内部,它看起来像这样 -

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

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

因此 UnitOfWork 通过依赖注入传递到 UserService 中,然后传递给 FluentValidation 类UserValidation",以便验证类可以执行数据库检查.我还将枚举传递给 UserValidation 类,以指定验证是用于更新还是创建.

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.

我尝试验证的 User 对象将具有诸如Role"和Company"之类的属性,并且我还为每个对象(RoleValidation 和 CompanyValidation)提供了单独的验证类.这两个验证类还将传入一个 UnitOfWork 以及这是创建还是更新.

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.

这是我的 UserValidation 类的示例 -

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

     }
}

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

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

所以现在这样做之后,我可以将 IValidator 注入到我的 UserService 构造函数中并使用它而不是在我的 UserService 方法中建立一个 Concrete 类.

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. 在您看来,按照我已经构建项目的方式,这是将依赖注入与 FluentValidation 结合使用并允许对服务方法进行单元测试以及对 FluentValidation 类进行单元测试的最佳方式吗?
  2. 是否有更好的方法使用依赖注入和 FluentValidation 来完成所有这些,同时让 FluentValidation 类知道它是创建"还是更新",而不是创建一个名为UserCreateValidation"的类" 和 "UserUpdateValidation" 还是将变量 "DatabaseOperation" 传递给 Validator 的构造函数?
  3. 在尝试设置 FluentValidation DependencyInjection 时附加到 (2) 我在传递DatabaseOperation"变量时遇到问题services.AddScoped>(x => new UserValidation(x.GetRequiredService<;IUnitOfWork>(), <如何判断是创建还是更新>));
  4. 最重要的是,我还必须在Startup.cs"文件中添加两行,以创建CompanyValidation"和RoleValidation"的作用域依赖注入,以在UserValidation"和两者中使用无论是更新还是创建,这些验证类也会传入.

任何帮助/建议将不胜感激.我真的被困在这个问题上.如果有人需要进一步澄清我面临的问题,请随时提出.

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.

谢谢

推荐答案

我面临着类似的问题.不管怎样,你帮了我.

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

我所做的不同/会有所不同.您可以使用规则集而不是创建或更新,根据名称,它将执行不同的规则集,这将允许您在验证时识别操作:https://fluentvalidation.net/start#rulesets.此时您不应该注入任何依赖于运行时结果的内容,例如创建或更新.

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. 我想我在上面指出了一个错误.否则对我来说看起来不错.不需要创建包装器来对您的验证进行单元测试,您可以像在此示例中一样简单地执行此操作:

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

问题 2:我只会向验证器注入一个 scopedFactory 并让它自己解决它的依赖项,而不是注入它需要的所有东西.但是你在 new CompanyValidator(_unitOfWork, databaseOperation) 里面做什么?在 Validator 中注入任何东西对我来说似乎很奇怪,因为它并不是你真正会注入的东西来解决规则.我不确定你的情况是什么,否则我会像我说的那样注入 scopedFactory 或嵌套类来做到这一点.

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:我想我已经回答了那个问题.

Question 3: I think I answered that one already.

问题 4:我会尝试创建一个通用的依赖注入,或者将一组验证器注入到某种工厂中,该工厂将根据类型进行解析.

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

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

哪个将根据类型解决我需要的验证器.

Which would resolve which validator I need based on type.

希望这是有道理的.

更新

所以在 CreateMethod 内部,将 RuleSet 名称传递给 validate 方法,让他解决是 Create 还是 Update.关于范围工厂 https://csharp.hotexamples.com/examples/-/IServiceScopeFactory/-/php-iservicescopefactory-class-examples.html

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

例如:取而代之的是:ValidationResult validationResult = 等待validation.ValidateAsync(user);

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

你可以这样做:

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

这是用法示例:

 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

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

这篇关于通过依赖注入在 .NET Core 中使用 FluentValidation的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆