当其基础 class 也具有验证时,如何对继承 class 的模型执行验证?

How to perform validation of a model of an inherited class when its base class also has validation?

好吧,我的问题是我正在使用 aspnetcore 2.1 创建一个 api,为了避免代码重复,我创建了一个抽象 class,其中包含共享 dtos 的属性(board、boardforcreation、板更新等)。我使用 validatableobject 添加到抽象 class 个性化验证,现在,我想向从抽象 class 派生的 classes 添加个性化验证,但它告诉我从 validatableobject 接口扩展是重复的,因为它已经在基础 class 中声明,而且当我在派生的 class 中添加 Validate 方法时,它告诉我它已经声明并实现了,那么,我如何在中添加验证抽象 class 和派生 class 使用可验证对象?还是有另一种方法来实现这一目标。提前谢谢你。

public class Board : BoardAbstractBase, IValidatableObject
{
    public Guid BoardId { get; set; }

    public DateTimeOffset StartDate { get; set; }

    public DateTimeOffset EndDate { get; set; }  
}

public abstract class BoardAbstractBase : AbstractBasicEntity, IValidatableObject
{
    public DateTimeOffset EstimatedStartDate { get; set; }


    public DateTimeOffset EstimatedEndDate { get; set; }


    public decimal EstimatedBudget { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!(EstimatedStartDate < EstimatedEndDate))
            yield return new ValidationResult(
                "StartDateBeforeEndDate|The estimated start date should be smaller than the end date.",
                new[] {"BoardAbstractBase"});
    }
}

虚拟 方法添加到您的基础 class。

如果您想在基础 class 中执行一些常见的验证逻辑,并在每个具体实现中执行额外的验证逻辑,请向您的基础 class 添加一个虚拟验证方法在基础 class 验证函数中调用。

加入你的基地class:

public abstract class BoardAbstractBase {
    ...
    protected virtual bool RepoValidate() {
        return true;
    }
    ...
}

然后在每个具体实现中实现 RepoValidate 以及您需要的任何自定义验证逻辑,如 protected override bool RepoValidate() {...}

例如

public class Board : BoardAbstractBase, IValidatableObject
{
    ...
    protected override bool RepoValidate() { 
        return this.BoardId == "";
    }
    ...
}

然后在BoardAbstractBase.Validate:

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!(EstimatedStartDate < EstimatedEndDate))
            yield return new ValidationResult(
                "StartDateBeforeEndDate|The estimated start date should be smaller than the end date.",
                new[] {"BoardAbstractBase"});

        if (!this.RepoValidate()){ ... }
    }

现在,如果验证失败,您可以随时将 RepoValidate 修改为 return 验证结果,或者采用任何参数,但为了举例,这个只是 return是假的。此外,因为它是 virtual 而不是 abstract,所以您只需要在需要执行额外的自定义逻辑时覆盖它。

使用 virtual 并使用 return yield。 IValidatableObject 是一个非常清晰的界面,具有非常清晰的意图 - 它在 Controllers 中使用,就像这样

if (ModelState.IsValid) {....}

因此,如果您的 Action 看起来像这样(干净整洁)

public Task<IActionResult> DoSomeAction(SomeClass model)
{
  if (ModelState.IsValid) {
    ...
  }
  else return View(model);
}

那么您的代码将类似于

 public BaseClass : IValidatableObject
{
// and implements
 public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (...)
                    yield return new ValidationResult($"...error - ", 
                        new string[] { "variable1 name" });
            
            if (...)
                    yield return new ValidationResult($"...error2 - ", 
                        new string[] { "variable1", "variable2" });
            
}

public class SomeClass : SomeBaseClass, IValidatableObject
{
    
// and implements
 public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
 {
   var baseErrors = base.Validate(validationContext);
        if (baseErrors != null && baseErrors.Any())
        {
            foreach (var item in baseErrors)
            {
                yield return item;
            }
        }
      if (...some error condition...)
                    yield return new ValidationResult($"Validation error - condition 1", 
                        new string[] { "variable1 });
            
}

我的意思是这是一个迭代过程——每个子类都必须“继承”其父类的所有验证检查,并在它们之上添加新的验证检查。

与已接受的答案类似,您可以将 Validate 方法的基础 Class 实现设为虚拟,然后在您的 child class 中覆盖 Validate 方法,return 首先继承结果,然后为 child class 添加自定义验证逻辑。请参阅下面的简化示例

public abstract class BoardBase: IValidatableObject
{
    public int Id { get; set; }

    public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var results = new List<ValidationResult>();

        if (Id != 1)
        {
            results.Add(new ValidationResult("Id must be 1"));
        }

        return results;
    }
}

public class Board: BoardBase
{
    public string Name { get; set; }

    public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var results = base.Validate(validationContext).ToList();

        if (Name.Length > 4)
        {
            results.Add(new ValidationResult("Name must be shorter than 5"));
        }

        return results;
    }
}