在错误的视图模型上使用验证器进行FluentValidation

时间:2019-05-16 18:17:56

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

我第一次使用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。

我经历了许多变体,试图弄清楚如何使其工作。这是我目前的状况。

Validator

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对TeamViewModels列表中的任何字段都没有任何验证规则。

联盟验证工厂

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. 如何告诉它针对哪个viewmodel使用哪个验证器?

我认为我一定会误解一些基本知识。

编辑

下面史蒂文(Steven)的回答使我指出了正确的方向!在进行了建议的更改之后,我遇到了另一个错误。一旦我解决了它,它就起作用了!这是我为使上面的代码正常工作所做的更改。

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。

这很重要!对于每个视图模型,它将尝试找到一个验证器。没有此空检查,将引发InvalidCastException。

依赖注入器

按照史蒂文的建议,我替换了容器。为此注册行:

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

这样可以避免每次添加新验证器时都明确列出每个验证器。

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

1 个答案:

答案 0 :(得分: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 see中得出的结论是,validatorTypeIValidator<T>类型的封闭通用版本,其中T是实际类型正在验证。这意味着,您将必须通过其IValidator<T>接口注册验证器。例如:

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

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

因此,更好的模型是使用自动注册,您可以在其中使用反射来注册所有IValidator<T>实现。幸运的是,您不必自己执行此操作; Simple Injector挺身而出:

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

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

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