3

Well, my problem is that I am creating an api using aspnetcore 2.1, to avoid code duplication I have created an abstract class with the properties that share the dtos (board, boardforcreation, boardforupdate, etc). I added to the abstract class personalized validation using ivalidatableobject, now, I want to add personalized validation to the classes that derive from the abstract class but it tells me that extending from ivalidatableobject interface is rebundant because it was already declared in the base class and also when I add the Validate method in the derived class it tells me that it is already declared and implemented, then, how can I add validation in an abstract class and in a derived class using ivalidatableobject? or is there another way to achieve this. Thank you in advance.

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"});
    }
}
PalaDiNNFoX
  • 33
  • 1
  • 4

3 Answers3

5

Add a virtual method to your base class.

If you want to perform some common validation logic in the base class as well as perform extra validation logic in each of the concrete implementations, then add an virtual validation method to your base class that is called within the base class validation function.

Add to your base class:

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

Then in each concrete implementation implement RepoValidate with whatever custom validation logic you need as protected override bool RepoValidate() {...}.

For Example

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

Then in 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()){ ... }
    }

Now, you can always modify RepoValidate to return a validation result if it fails, or take any argument, but just for the sake of example, this one simply returns false. Also, because it is virtual and not abstract, you only need to override it when you have extra custom logic to perform.

Mattkwish
  • 753
  • 7
  • 16
1

Use virtual and use return yield. IValidatableObject is a very clear interface with a very clear intent in mind - it is used in the Controllers like this

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

So if your Action looks something like this (nice and clean)

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

then your code will be something like

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

my point being that this is an iterative process - each subclass must "inherit" all the validation checks of its parents, and add new validation checks on top of them.

LongChalk
  • 783
  • 8
  • 13
0

Similar to the accepted answer, what you could is make the Base Class implementation of the Validate method virtual then in your child class, override the Validate method, return inherited result first then add custom validation logic for the child class. See simplified example below

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;
    }
}
Josef
  • 2,869
  • 2
  • 22
  • 23