fluentvalidation
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了fluentvalidation相关的知识,希望对你有一定的参考价值。
参考技术A FluentValidation 有几个内置的验证器,这些验证器的错误消息都可以使用特定占位符。说明:确保指定的属性不是 null。
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
PropertyValue = 属性的当前值
说明: 确保指定的属性不是 null、空字符串或空格 (或值类型的默认值, 例如 int 0)。
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
PropertyValue = 属性的当前值
说明: 确保指定属性的值不等于特定值 (或不等于其他属性的值)
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
ComparisonValue = 属性不应等于的值
说明: 确保指定属性的值等于特定值 (或等于另一个属性的值)
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
ComparisonValue = 属性应相等的值
PropertyValue = 属性的当前值
确保特定字符串属性的长度位于指定范围内。但是, 它不能确保字符串属性是否为 null。
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
MinLength = 最小长度
MaxLength = 最大长度
TotalLength = 输入的字符数
PropertyValue = 属性的当前值
说明:确保特定字符串属性的长度不超过指定的值。
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
MaxLength = 最大长度
TotalLength = 输入的字符数
PropertyValue = 属性的当前值
说明:确保特定字符串属性的长度不能小于指定的值。
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
MinLength = 最小长度
TotalLength = 输入的字符数
PropertyValue = 属性的当前值
说明: 确保指定属性的值小于特定值 (或小于另一个属性的值)
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
ComparisonValue-属性比较的值
PropertyValue = 属性的当前值
说明: 确保指定属性的值小于等于特定值 (或小于等于另一个属性的值)
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
ComparisonValue-属性比较的值
PropertyValue = 属性的当前值
说明: 确保指定属性的值大于特定值 (或大于另一个属性的值)
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
ComparisonValue-属性比较的值
PropertyValue = 属性的当前值
说明: 确保指定属性的值大于等于特定值 (或大于等于另一个属性的值)
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
ComparisonValue-属性比较的值
PropertyValue = 属性的当前值
描述: 将指定属性的值传递到一个委托中, 可以对该值执行自定义验证逻辑
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
PropertyValue = 属性的当前值
请注意, 委托参数不仅传递参数,还支持直接传递验证对象参数:
说明: 确保指定属性的值与给定的正则表达式匹配,正则表达式可参阅 正则表达式教程 这篇文章
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
PropertyValue = 属性的当前值
说明: 确保指定属性的值是有效的电子邮件地址格式。
可用的格式参数占位符:
PropertyName = 正在验证的属性的名称
PropertyValue = 属性的当前值
实现自定义验证程序的最简单方法是使用方法 Must 方法,假设我们有以下类:
为了确保列表中至少包含10个元素, 我们可以这样做:
为了使这种逻辑可重用, 我们可以将其封装为扩展方法。
在这里,我们通过为 IRuleBuilder 创建扩展方法实现可重用逻辑,使用方法很简单。
如果您想灵活控制可重用的验证器, 则可以使用 Must 方法编写自定义规则,此方法允许您手动创建与验证错误关联的实例。
此方法的优点是它允许您为同一规则返回多个错误。
在某些情况下, 针对某些属性的验证逻辑非常复杂, 我们希望将基于属性的自定义逻辑移动到单独的类中,可通过重写 PropertyValidator 类来完成。
继承 PropertyValidator 时, 必须重写 IsValid 方法,此方法接受一个对象, 并返回一个布尔值, 指示验证是否成功,可通过 PropertyValidatorContext 属性访问:
Instance-正在验证的对象
PropertyDescription-属性的名称 (或者是由调用 WithName 的自定义的别名)
PropertyValue-正在验证的属性值
Member-描述正在验证的属性的 MemberInfo 信息
若要使用自定义的属性验证程序, 可以在定义验证规则时调用:
在 .NET Core 中使用 FluentValidation 和依赖注入
【中文标题】在 .NET Core 中使用 FluentValidation 和依赖注入【英文标题】:Using FluentValidation in .NET Core with Dependency Injection 【发布时间】:2019-05-18 20:22:40 【问题描述】:我有一个 .NET Core Web Api 应用程序,它按以下方式排列 -
-
注入业务服务的控制器层
注入工作单元的业务服务(与数据库交互)
业务服务也可能调用 FluentValidation 类
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 类,以指定验证是针对更新还是针对创建。
我尝试验证的用户对象将具有诸如“角色”和“公司”之类的属性,并且我还为每个对象设置了单独的验证类(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 方法中看到的那样,我为我的验证实例化了具体类。因此,为了做到这一点,我必须为我的每个 fluentvalidation 类创建一个接口,并将它们注入到使用它们的业务服务中。所以我在我的 Startup.cs 文件中做了以下操作 -
services.AddScoped<IValidator<User>>(x => new UserValidation(x.GetRequiredService<IUnitOfWork>()));
所以现在在这样做之后,我可以将 IValidator 注入到我的 UserService 构造函数中并使用它,而不是在我的 UserService 方法中实例化一个具体类。
因此,我提出以下问题。
-
在您看来,我已经构建项目的方式是使用 FluentValidation 的依赖注入并允许对服务方法进行单元测试以及 FluentValidation 类的单元测试的最佳方式吗?
有没有更好的方法使用依赖注入和 FluentValidation 来完成所有这些,同时让 FluentValidation 类知道它是“创建”还是“更新”,而不是创建一个名为“UserCreateValidation”的类" 和 "UserUpdateValidation" 还是将变量 "DatabaseOperation" 传递给 Validator 的构造函数?
尝试设置 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>));
除此之外,我还必须在“Startup.cs”文件中添加两行,以创建“CompanyValidation”和“RoleValidation”的Scoped DependencyInjection,以便在“UserValidation”和两者中使用无论是更新还是创建,这些验证类也会传入。
任何帮助/建议将不胜感激。我真的被这个问题困住了。如果有人需要进一步澄清我所面临的问题,请随时提出。
谢谢
【问题讨论】:
【参考方案1】:我面临着类似的问题。但是你帮了我。
我做了不同的事/会做不同的事。您可以使用 RuleSets 而不是 Create 或 Update,这取决于它将执行不同 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 或嵌套类来做到这一点。
问题 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 验证结果 = 等待验证.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
【讨论】:
您好,非常感谢您的回复。我面临的主要问题是公司验证器将在用户验证器内部被调用。我可以添加规则集(我调查过)。所以我可以在用户验证器中有一个用于创建的规则集和一个用于更新的规则集。但问题是我如何告诉公司验证器(在用户验证器内部)使用哪个规则集。公司验证器还将有 2 个规则集(一个用于创建,一个用于更新)。基本上我希望公司验证器继承用户验证器调用的任何规则集。 另外,您介意解释一下,或者给出一个简短的例子来说明您对 (2) 的回答。我不熟悉使用 scopedfactory 的。谢谢 好的,感谢您的详细回复。也许你误解了其中一个问题。因此,您提供了使用规则集的示例 - validator.Validate(person, ruleSet: "Create");但请记住,“validator”类也有一个“CompanyValidator”。我如何告诉“CompanyValidator”使用哪个规则集(如何以某种方式传递它)? 其实我想我在这里找到了答案:https://github.com/JeremySkinner/FluentValidation/issues/194。好像如果我这样做 validator.Validator(person, ruleSet: "Create") 并且我在里面有另一个验证器,例如 CompanyValidator (并且该公司验证器也有一个“创建”规则集)它也将被执行而不必通过任何东西 很高兴你做到了! :)以上是关于fluentvalidation的主要内容,如果未能解决你的问题,请参考以下文章