【问题标题】:Accessing the enclosing viewmodel when using FluentValidation on an inner viewmodel在内部视图模型上使用 FluentValidation 时访问封闭视图模型
【发布时间】:2019-05-21 19:20:37
【问题描述】:

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

项目类

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

任务类

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

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

ProjectValidator 类

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,像这样:

TaskValidator 类

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,如下所示:

ProjectValidator 类(修改)

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

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

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

【问题讨论】:

    标签: c# validation fluentvalidation


    【解决方案1】:

    我不明白你在做这样的事情

     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 时设置此属性并不难。

    【讨论】:

    • 我可以将项目视图模型添加到任务视图模型中,但项目与任务同时输入(即它是一个新项目,因此用户输入项目日期,然后在同一屏幕上输入带有日期的服务器任务)。尽管“外部”项目视图模型会在用户单击提交按钮时包含用户的输入,但“内部”项目视图模型在验证发生时不会包含该信息。 (我想我可以在提交时将一堆 JavaScript 复制到隐藏字段中,但我想避免这种情况。)
    【解决方案2】:

    在 FluentValidation 的作者 Jeremy Skinner 的帮助下,我得以完成这项工作!

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

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

    验证器工厂

    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,而不是通过验证器工厂。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-25
      • 1970-01-01
      • 1970-01-01
      • 2014-08-15
      • 2013-01-04
      • 2018-06-24
      相关资源
      最近更新 更多