【问题标题】:FluentValidation using validator on wrong viewmodelFluentValidation 在错误的视图模型上使用验证器
【发布时间】:2019-05-16 18:17:55
【问题描述】:

我是第一次使用 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:无法将“TeamViewModel”类型的对象转换为“LeagueViewModel”类型。在 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>();
    }
}

依赖注入器

我们将 SimpleInjector 用于 DI。作为现有设置的一部分,它正在调用一个方法来注册服务。在该方法中,我添加了对此的调用:

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

问题

  1. 如何让它正常工作?
  2. 为什么要尝试使用 LeagueValidator 来验证 TeamViewModel?
  3. 是否需要为每个视图模型设置单独的验证器和验证器工厂?
  4. 即使是那些没有任何验证规则的?
  5. 如何告诉它为哪个视图模型使用哪个验证器?

我想我一定是误解了一些基本的东西。

编辑

史蒂文在下面的回答让我指出了正确的方向!在我做出他建议的更改后,我遇到了另一个错误。一旦我解决了这个问题,它就可以工作了!以下是我为使上述代码正常工作所做的更改。

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,如果没有找到则返回 null。

这很重要!对于每个视图模型,它都会尝试找到一个验证器。如果没有这个 null 检查,就会抛出 InvalidCastException。

依赖注入器

按照 Steven 的建议,我将 container.Register 替换为:

container.Register(typeof(IValidator<>), new[] { typeof(SimpleInjectorInitializer).Assembly });

这避免了每次添加新验证器时都需要明确列出每个验证器。

现在一切正常!非常感谢您的帮助,史蒂文!

【问题讨论】:

    标签: c# validation dependency-injection simple-injector fluentvalidation


    【解决方案1】:

    我不熟悉 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);
    }
    

    我在 FluentValidator 源代码中的can seevalidatorTypeIValidator&lt;T&gt; 类型的封闭通用版本,T 是正在验证的实际类型。这意味着,您必须通过其IValidator&lt;T&gt; 接口注册验证器。例如:

    container.Register<IValidator<LeagueViewModel>, LeagueValidator>();
    

    这种配置为代码(或显式注册)模型,您使用一行代码显式注册每个验证器,如果您只有几个验证器,可能会正常工作,但这通常会导致在必须经常更新的Composition Root 中。

    因此,更好的模型是使用自动注册,您可以在其中使用反射注册所有IValidator&lt;T&gt; 实现。幸运的是,您不必自己实现它; Simple Injector 支持您:

    var validatorAssemblies = new[] { typeof(LeagueValidator).Assembly };
    container.Register(typeof(IValidator<>), validatorAssemblies);
    

    这可确保您在刚刚添加新验证器(在该特定程序集中)时不必更改合成根。

    使用此设置,我认为您没有理由使用FluentValidation.Attributes.ValidatorAttribute 标记您的视图模型。如果可以,请删除它,因为它只会导致您的视图模型和验证器之间的不紧密耦合。

    【讨论】:

    • 您的回复让我走上了正轨!在我做出您建议的更改后,我收到了一条不同的错误消息,它与 FluentValidation 的工作方式更直接相关。我很快就弄清楚了,所以现在可以了!我已经编辑了我的原始问题以显示我需要对我的代码进行的更改。非常感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-17
    • 1970-01-01
    相关资源
    最近更新 更多