在内部视图模型上使用 FluentValidation 时访问封闭的视图模型

Accessing the enclosing viewmodel when using FluentValidation on an inner viewmodel

假设我有 2 个视图模型,项目和任务。该项目包含一个任务列表。该项目有一个 BeginDate 和一个 EndDate。每个任务都有一个 BeginDate 和一个 EndDate。

项目class

public class Project
{
  public DateTime BeginDate { get; set; }
  public DateTime EndDate { get; set; }
  public List<Task> Tasks { get; set; }
}

任务class

public class Task
{
  public DateTime BeginDate { get; set; }
  public DateTime EndDate { get; set; }
}

我想使用 FluentValidation 来验证任务中的每个日期是否都在项目的日期之内。我可以这样做:

项目验证器class

public class ProjectValidator : AbstractValidator<Project>
{
  public ProjectValidator()
  {
    RuleForEach(x => x.Tasks)
      .Must(((p, t) => t.BeginDate >= p.BeginDate && t.BeginDate <= p.EndDate))
      .WithMessage("Task Begin Date must be within Project's Begin and End Dates.");

    RuleForEach(x => x.Tasks)
      .Must(((p, t) => t.EndDate >= p.BeginDate && t.EndDate <= p.EndDate))
      .WithMessage("Task End Date must be within Project's Begin and End Dates.");
  }
}

但是,这不会标记出问题的特定任务开始或结束日期。错误消息显示在错误摘要的屏幕顶部,而不是问题日期旁边。可能有十几个或更多的日期需要检查,因此很难确定哪一个是真正的问题。

大概这是因为我在项目级别而不是单个任务级别进行验证。要获取任务中标记有错误消息的字段,我需要验证任务。

所以我需要写一个 TaskValidator,像这样:

任务验证器class

public class TaskValidator : AbstractValidator<Task>
{
  Project _proj;

  public TaskValidator(Project proj)
  {
    _proj = proj;

    RuleFor(x => x.BeginDate)
      .Must(BeWithinProjectDates)
      .WithMessage("Task Begin Date must be within Project's Begin and End Dates.");

    RuleFor(x => x.EndDate)
      .Must(BeWithinProjectDates)
      .WithMessage("Task End Date must be within Project's Begin and End Dates.");
  }

  private bool BeWithinProjectDates(DateTime date)
  {
    return (date >= _proj.BeginDate &&
            date <= _proj.EndDate);
  }
}

然后我可以在规则中将项目视图模型传递给 TaskValidator,如下所示:

ProjectValidatorclass(已修改)

public class ProjectValidator : AbstractValidator<Project>
{
  public ProjectValidator()
  {
    RuleForEach(x => x.Tasks)
      .SetValidator(x => new TaskValidator(x));
  }
}

不幸的是,这不起作用。 TaskValidator 构造函数被调用一次,以初始化验证器。验证发生时不会调用它。这意味着 Project 对象是空的。

有什么方法可以从 TaskValidator 中引用项目视图模型,以便我可以访问项目开始日期和结束日期的值?

我正确理解你正在做这样的事情

 RuleForEach(p => p.Tasks).SetValidator(project => new TaskValidator(project));

它不起作用? 为什么不直接将 Project 属性 添加到您的 Task ViewModel 中?

public class Task
{
    public DateTime BeginDate { get; set; }
    public DateTime EndDate { get; set; }

    public Project Project { get; set; }
}

创建 ViewModel 时设置此 属性 并不难。

在 FluentValidation 的作者杰里米·斯金纳 (Jeremy Skinner) 的帮助下,我得以完成这项工作!

首先,SetValidator参数必须是一个lambda表达式。这将导致它在适当的时间被调用。我实际上已经在这样做了,尽管我在发布问题时错误地忽略了它。

其次,我使用 SimpleInjector 和验证器工厂来实现依赖注入。这意味着我必须 不注册 TaskValidator 或确保工厂没有 return 一个。

验证器工厂

public class ValidatorFactory : ValidatorFactoryBase
{
    private readonly Container _container;

    public ValidatorFactory(Container container)
    {
        _container = container;
    }

    public override IValidator CreateInstance(Type validatorType)
    {
        if (_container.GetRegistration(validatorType) == null)
        {
            return null;
        }

        if (validatorType == typeof(IValidator<Task>))
        {
            return null;
        }

        return (IValidator)_container.GetInstance(validatorType);
    }
}

这将确保 SetValidator 调用中的 lambda 表达式直接创建 TaskValidator,而不是通过验证器工厂。