在具有依赖注入的 .NET Core 中使用 FluentValidation
Using FluentValidation in .NET Core with Dependency Injection
我有一个 .NET Core Web Api 应用程序,其排列方式如下 -
- 注入业务服务的控制器层
- 注入工作单元(与数据库交互)的业务服务
- 业务服务也可能调用 FluentValidation class
- FluentValidation 将注入工作单元以执行数据库检查(存在等)
所以这里说了所有这些都是一个例子。如果我想在系统中创建一个用户,我有一个位于 "UsersController" 内部的名为 "PostUser" 的 route/method。 "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 class "UserValidation" 因此验证 class 可以执行数据库检查。我还将枚举传递到 UserValidation class 以指定验证是用于更新还是创建。
我尝试验证的用户对象将具有 "Role" 和 "Company" 等属性,我还对每个对象(RoleValidation 和 CompanyValidation)进行了单独的验证 classes。这两个验证 classes 还将传递一个 UnitOfWork 以及这是创建还是更新。
这是我的用户验证的示例 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" class 创建单元测试。但我相信为了正确地做到这一点,我需要在某些情况下模拟 FluentValidation class,正如你在我的 "UserService" CreateUser 方法中看到的那样,我为我的 class 实例化了具体的 class验证。因此,为了做到这一点,我必须为我的每个 fluentvalidation classes 创建一个接口,并将它们注入到使用它们的业务服务中。所以我在 Startup.cs 文件中做了以下操作 -
services.AddScoped<IValidator<User>>(x => new UserValidation(x.GetRequiredService<IUnitOfWork>()));
所以现在在这样做之后我可以将 IValidator 注入我的 UserService 构造函数并使用它而不是在我的 UserService 方法中实例化具体 class。
因此,我想问以下问题。
- 在您看来,我已经构建项目的方式是否是将依赖注入与 FluentValidation 结合使用并允许对服务方法进行单元测试以及对 FluentValidation 进行单元测试的最佳方式 class ?
- 有没有更好的方法使用依赖注入和 FluentValidation 来完成所有这些,同时让 FluentValidation class 知道它是 "Create" 还是 "Update",而不是创建一个名为 "UserCreateValidation" 和 "UserUpdateValidation" 的 class 或将变量 "DatabaseOperation" 传递给验证器的构造函数?
- 在尝试设置 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" 的 Scoped DependencyInjection 和 "RoleValidation" 以在内部使用"UserValidation" 并且这两个验证 classes 也将传递进来,无论它是更新还是创建。
任何 help/suggestions 将不胜感激。我真的被这个问题困住了。如果有人需要进一步澄清我面临的问题,请不要犹豫。
谢谢
我遇到了类似的问题。但是你帮了我。
我所做的 differently/Would 有所不同。您可以使用 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 或 Nested class 来做到这一点。
问题 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 validationResult = await validation.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
我有一个 .NET Core Web Api 应用程序,其排列方式如下 -
- 注入业务服务的控制器层
- 注入工作单元(与数据库交互)的业务服务
- 业务服务也可能调用 FluentValidation class
- FluentValidation 将注入工作单元以执行数据库检查(存在等)
所以这里说了所有这些都是一个例子。如果我想在系统中创建一个用户,我有一个位于 "UsersController" 内部的名为 "PostUser" 的 route/method。 "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 class "UserValidation" 因此验证 class 可以执行数据库检查。我还将枚举传递到 UserValidation class 以指定验证是用于更新还是创建。
我尝试验证的用户对象将具有 "Role" 和 "Company" 等属性,我还对每个对象(RoleValidation 和 CompanyValidation)进行了单独的验证 classes。这两个验证 classes 还将传递一个 UnitOfWork 以及这是创建还是更新。
这是我的用户验证的示例 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" class 创建单元测试。但我相信为了正确地做到这一点,我需要在某些情况下模拟 FluentValidation class,正如你在我的 "UserService" CreateUser 方法中看到的那样,我为我的 class 实例化了具体的 class验证。因此,为了做到这一点,我必须为我的每个 fluentvalidation classes 创建一个接口,并将它们注入到使用它们的业务服务中。所以我在 Startup.cs 文件中做了以下操作 -
services.AddScoped<IValidator<User>>(x => new UserValidation(x.GetRequiredService<IUnitOfWork>()));
所以现在在这样做之后我可以将 IValidator 注入我的 UserService 构造函数并使用它而不是在我的 UserService 方法中实例化具体 class。
因此,我想问以下问题。
- 在您看来,我已经构建项目的方式是否是将依赖注入与 FluentValidation 结合使用并允许对服务方法进行单元测试以及对 FluentValidation 进行单元测试的最佳方式 class ?
- 有没有更好的方法使用依赖注入和 FluentValidation 来完成所有这些,同时让 FluentValidation class 知道它是 "Create" 还是 "Update",而不是创建一个名为 "UserCreateValidation" 和 "UserUpdateValidation" 的 class 或将变量 "DatabaseOperation" 传递给验证器的构造函数?
- 在尝试设置 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" 的 Scoped DependencyInjection 和 "RoleValidation" 以在内部使用"UserValidation" 并且这两个验证 classes 也将传递进来,无论它是更新还是创建。
任何 help/suggestions 将不胜感激。我真的被这个问题困住了。如果有人需要进一步澄清我面临的问题,请不要犹豫。
谢谢
我遇到了类似的问题。但是你帮了我。
我所做的 differently/Would 有所不同。您可以使用 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 或 Nested class 来做到这一点。
问题 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 validationResult = await validation.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