FluentValidation 在错误的视图模型上使用验证器
FluentValidation using validator on wrong viewmodel
我是第一次使用 FluentValidation。我有一些基本的验证工作,但后来我意识到我需要为一些更复杂的验证做一些数据库检索。这需要进行依赖注入,这样我才能使用数据库服务,这让我陷入了目前的状态:卡住了。我无法让它工作。
为了简化事情,我假设我的应用程序正在处理体育联盟和球队,因为我认为这是一个比合同、发票、资金来源、供应商和分包商更简单的心智模型。 :-)
所以,假设我有一个体育联盟的视图模型。在该视图模型中,存在该联赛中球队的视图模型集合。
我有一个编辑联赛的屏幕。同一屏幕允许更改有关该联盟中球队的一些信息。
LeagueViewModel
联赛的视图模型包含球队的视图模型列表。
[FluentValidation.Attributes.Validator(typeof(LeagueValidator))]
public class LeagueViewModel
{
public string LeagueName { get; set; }
public DateTime SeasonBeginDate { get; set; }
public DateTime SeasonEndDate { get; set; }
public List<TeamViewModel> TeamViewModels { get; set; }
}
我已经为 LeagueViewModel 创建了一个验证器。不幸的是,当我编辑联赛并单击提交按钮时,我收到此错误消息:
InvalidCastException: Unable to cast object of type 'TeamViewModel' to type 'LeagueViewModel'. at FluentValidation.ValidationContext.ToGenericT
显然它正在尝试使用 LeagueValidator 验证 TeamViewModel。
我经历了 许多 变体,试图弄清楚如何让它工作。这是我目前拥有的。
验证者
public class LeagueValidator : AbstractValidator<LeagueViewModel>
{
private readonly ILeagueService _leagueService;
public LeagueValidator(ILeagueService leagueService)
{
_leagueService = leagueService;
RuleFor(x => x.SeasonEndDate)
.NotNull()
.GreaterThan(x => x.SeasonBeginDate)
.WithMessage("Season End Date must be later than Season Begin Date.");
}
}
(LeagueService 位在那里,因为在实际代码中它需要检查一些数据库值,它使用服务来检索这些值。)
请注意,LeagueValidator 对 TeamViewModel 列表中的任何字段都没有任何验证规则。
联盟验证器工厂
public class LeagueValidatorFactory : ValidatorFactoryBase
{
private readonly Container _container;
public LeagueValidatorFactory(Container container)
{
_container = container;
}
public override IValidator CreateInstance(Type validatorType)
{
return _container.GetInstance<LeagueValidator>();
}
}
依赖注入器
我们正在为 DI 使用 SimpleInjector。作为现有设置的一部分,它正在调用一种方法来注册服务。在该方法中,我添加了对此的调用:
private static void RegisterValidators(Container container)
{
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
var leagueValidatorProvider =
new FluentValidationModelValidatorProvider(new LeagueValidatorFactory(container));
leagueValidatorProvider.AddImplicitRequiredValidator = false;
ModelValidatorProviders.Providers.Add(leagueValidatorProvider);
container.Register<LeagueValidator>();
}
问题
- 如何让它正常工作?
- 为什么要尝试使用 LeagueValidator 来验证 TeamViewModel?
- 我是否需要为每个视图模型设置单独的验证器和验证器工厂?
- 甚至那些没有任何验证规则的?
- 如何告诉它哪个验证器用于哪个视图模型?
我想我一定是误解了一些基本的东西。
编辑
下面史蒂文的回复让我找到了正确的方向!在我按照他的建议进行更改后,我遇到了另一个错误。一旦我修好了,它就可以了!以下是我为使上述代码正常工作所做的更改。
LeagueViewModel
我删除了这一行,因为它不是必需的。
[FluentValidation.Attributes.Validator(typeof(LeagueValidator))]
LeagueValidatorFactory
我将其重命名为"ValidatorFactory",因为事实证明无论我创建多少个验证器,都只会有一个验证器工厂。然后我将 CreateInstance 方法更改为:
public override IValidator CreateInstance(Type validatorType)
{
if (_container.GetRegistration(validatorType) == null)
{
return null;
}
return (IValidator)_container.GetInstance(validatorType);
}
这不再明确指定要获取的验证器类型(这就是为什么只需要一个工厂)。为了确定给定类型的验证器是否可用,它会调用 GetRegistration,如果找到 none 则返回 null。
这很重要!对于每个视图模型,它将尝试找到一个验证器。如果没有此 null 检查,则会抛出 InvalidCastException。
依赖注入器
根据 Steven 的建议,我将 container.Register 行替换为:
container.Register(typeof(IValidator<>), new[] { typeof(SimpleInjectorInitializer).Assembly });
这避免了每次添加新验证器时都需要显式列出每个验证器。
现在一切正常了!非常感谢你的帮助,史蒂文!
我不熟悉 FluentValidation,但您的 LeagueValidatorFactory
似乎从容器中请求了错误的类型,考虑到它提供了要验证的类型。
因此,我希望您的验证工厂看起来像这样:
public class LeagueValidatorFactory : ValidatorFactoryBase
{
private readonly Container _container;
public LeagueValidatorFactory(Container container) =>
_container = container;
public override IValidator CreateInstance(Type validatorType) =>
(IValidator)_container.GetInstance(validatorType);
}
我 can see 从 FluentValidator 源代码中得知,validatorType
是 IValidator<T>
类型的 closed-generic 版本,具有 T
是被验证的实际类型。这意味着,您必须通过 IValidator<T>
接口注册验证器。例如:
container.Register<IValidator<LeagueViewModel>, LeagueValidator>();
这种配置即代码(或explicit-register)模型,您可以在其中使用一行代码显式注册每个验证器,如果您只有几个验证器,但这通常会导致 Composition Root 必须经常更新。
因此,更好的模型是使用 Auto-Registration,在其中使用反射注册所有 IValidator<T>
实现。幸运的是,您不必自己实现它; Simple Injector 支持您:
var validatorAssemblies = new[] { typeof(LeagueValidator).Assembly };
container.Register(typeof(IValidator<>), validatorAssemblies);
这确保您在刚刚添加新验证器(在该特定程序集中)时永远不必更改组合根。
有了这个设置,我看不出你为什么要用 FluentValidation.Attributes.ValidatorAttribute
标记你的视图模型。如果可以,请将其删除,因为它只会导致您的视图模型和验证器之间的耦合不紧密。
我是第一次使用 FluentValidation。我有一些基本的验证工作,但后来我意识到我需要为一些更复杂的验证做一些数据库检索。这需要进行依赖注入,这样我才能使用数据库服务,这让我陷入了目前的状态:卡住了。我无法让它工作。
为了简化事情,我假设我的应用程序正在处理体育联盟和球队,因为我认为这是一个比合同、发票、资金来源、供应商和分包商更简单的心智模型。 :-)
所以,假设我有一个体育联盟的视图模型。在该视图模型中,存在该联赛中球队的视图模型集合。
我有一个编辑联赛的屏幕。同一屏幕允许更改有关该联盟中球队的一些信息。
LeagueViewModel
联赛的视图模型包含球队的视图模型列表。
[FluentValidation.Attributes.Validator(typeof(LeagueValidator))]
public class LeagueViewModel
{
public string LeagueName { get; set; }
public DateTime SeasonBeginDate { get; set; }
public DateTime SeasonEndDate { get; set; }
public List<TeamViewModel> TeamViewModels { get; set; }
}
我已经为 LeagueViewModel 创建了一个验证器。不幸的是,当我编辑联赛并单击提交按钮时,我收到此错误消息:
InvalidCastException: Unable to cast object of type 'TeamViewModel' to type 'LeagueViewModel'. at FluentValidation.ValidationContext.ToGenericT
显然它正在尝试使用 LeagueValidator 验证 TeamViewModel。
我经历了 许多 变体,试图弄清楚如何让它工作。这是我目前拥有的。
验证者
public class LeagueValidator : AbstractValidator<LeagueViewModel>
{
private readonly ILeagueService _leagueService;
public LeagueValidator(ILeagueService leagueService)
{
_leagueService = leagueService;
RuleFor(x => x.SeasonEndDate)
.NotNull()
.GreaterThan(x => x.SeasonBeginDate)
.WithMessage("Season End Date must be later than Season Begin Date.");
}
}
(LeagueService 位在那里,因为在实际代码中它需要检查一些数据库值,它使用服务来检索这些值。)
请注意,LeagueValidator 对 TeamViewModel 列表中的任何字段都没有任何验证规则。
联盟验证器工厂
public class LeagueValidatorFactory : ValidatorFactoryBase
{
private readonly Container _container;
public LeagueValidatorFactory(Container container)
{
_container = container;
}
public override IValidator CreateInstance(Type validatorType)
{
return _container.GetInstance<LeagueValidator>();
}
}
依赖注入器
我们正在为 DI 使用 SimpleInjector。作为现有设置的一部分,它正在调用一种方法来注册服务。在该方法中,我添加了对此的调用:
private static void RegisterValidators(Container container)
{
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
var leagueValidatorProvider =
new FluentValidationModelValidatorProvider(new LeagueValidatorFactory(container));
leagueValidatorProvider.AddImplicitRequiredValidator = false;
ModelValidatorProviders.Providers.Add(leagueValidatorProvider);
container.Register<LeagueValidator>();
}
问题
- 如何让它正常工作?
- 为什么要尝试使用 LeagueValidator 来验证 TeamViewModel?
- 我是否需要为每个视图模型设置单独的验证器和验证器工厂?
- 甚至那些没有任何验证规则的?
- 如何告诉它哪个验证器用于哪个视图模型?
我想我一定是误解了一些基本的东西。
编辑
下面史蒂文的回复让我找到了正确的方向!在我按照他的建议进行更改后,我遇到了另一个错误。一旦我修好了,它就可以了!以下是我为使上述代码正常工作所做的更改。
LeagueViewModel
我删除了这一行,因为它不是必需的。
[FluentValidation.Attributes.Validator(typeof(LeagueValidator))]
LeagueValidatorFactory
我将其重命名为"ValidatorFactory",因为事实证明无论我创建多少个验证器,都只会有一个验证器工厂。然后我将 CreateInstance 方法更改为:
public override IValidator CreateInstance(Type validatorType)
{
if (_container.GetRegistration(validatorType) == null)
{
return null;
}
return (IValidator)_container.GetInstance(validatorType);
}
这不再明确指定要获取的验证器类型(这就是为什么只需要一个工厂)。为了确定给定类型的验证器是否可用,它会调用 GetRegistration,如果找到 none 则返回 null。
这很重要!对于每个视图模型,它将尝试找到一个验证器。如果没有此 null 检查,则会抛出 InvalidCastException。
依赖注入器
根据 Steven 的建议,我将 container.Register 行替换为:
container.Register(typeof(IValidator<>), new[] { typeof(SimpleInjectorInitializer).Assembly });
这避免了每次添加新验证器时都需要显式列出每个验证器。
现在一切正常了!非常感谢你的帮助,史蒂文!
我不熟悉 FluentValidation,但您的 LeagueValidatorFactory
似乎从容器中请求了错误的类型,考虑到它提供了要验证的类型。
因此,我希望您的验证工厂看起来像这样:
public class LeagueValidatorFactory : ValidatorFactoryBase
{
private readonly Container _container;
public LeagueValidatorFactory(Container container) =>
_container = container;
public override IValidator CreateInstance(Type validatorType) =>
(IValidator)_container.GetInstance(validatorType);
}
我 can see 从 FluentValidator 源代码中得知,validatorType
是 IValidator<T>
类型的 closed-generic 版本,具有 T
是被验证的实际类型。这意味着,您必须通过 IValidator<T>
接口注册验证器。例如:
container.Register<IValidator<LeagueViewModel>, LeagueValidator>();
这种配置即代码(或explicit-register)模型,您可以在其中使用一行代码显式注册每个验证器,如果您只有几个验证器,但这通常会导致 Composition Root 必须经常更新。
因此,更好的模型是使用 Auto-Registration,在其中使用反射注册所有 IValidator<T>
实现。幸运的是,您不必自己实现它; Simple Injector 支持您:
var validatorAssemblies = new[] { typeof(LeagueValidator).Assembly };
container.Register(typeof(IValidator<>), validatorAssemblies);
这确保您在刚刚添加新验证器(在该特定程序集中)时永远不必更改组合根。
有了这个设置,我看不出你为什么要用 FluentValidation.Attributes.ValidatorAttribute
标记你的视图模型。如果可以,请将其删除,因为它只会导致您的视图模型和验证器之间的耦合不紧密。