1

I have a web api with some nested models that are being bound like so:

public class Class1 {
  //This data is needed in Class2 and Class3 validation
  public bool ImportantInformation {get; set;}
  public Class2 A {get; set;}
  public Class3 B {get; set;}
}

public class Class2 {
  [ConditionallyValidatedAttribute]
  public string X {get; set;}
}

public class Class3 {
  [ConditionallyValidatedAttribute]
  public string Y {get; set;}
}

ConditionallyValidatedAttribute looks like this:

public sealed class ConditionallyValidatedAttribute : ValidationAttribute {
  protected override ValidationResult IsValid(object value, ValidationContext vc) {
    bool importantInformation = //How do I get Class1.ImportantInformation?
    if(importantImformation && value == null) {
      return new ValidationResult($"{vc.MemberName} is invalid");
    }
    return ValidationResult.Success;
  }
}

My question is is there some way to inject ImportantInformation into ConditionallyValidatedAttribute as needed?

Benjamin U.
  • 580
  • 1
  • 3
  • 16

2 Answers2

1

As far as I know - there is no way to do that, as instance A itself is not aware that it belongs to parent.
But there is another way. Instead of applying ConditionallyValidatedAttribute to property X or Y, you can apply it to entire A or B inside of your parent. Doing that you can have access to the whole A and ImportantInformation inside of your IsValid method. Something like that:

public class Class1 
{
    public bool ImportantInformation {get; set;}
    [ConditionallyValidatedAttribute] //-->Validation Attribute here
    public Class2 A {get; set;}
    public Class3 B {get; set;}
}

public class Class2 
{
    public string X {get; set;}
}  

public sealed class ConditionallyValidatedAttribute : ValidationAttribute 
{
    protected override ValidationResult IsValid(object value, ValidationContext vc) 
    {
        var importantInformation = bool.Parse(vc.ObjectType.GetProperty("ImportantInformation")?.GetValue(vc.ObjectInstance)?.ToString() ?? "false");
        if (importantInformation && (!(value is Class2) || string.IsNullOrEmpty(((Class2)value).X)))
        {
            return new ValidationResult($"{vc.MemberName} is invalid");
        }

        return ValidationResult.Success;
    }
}
Roman.Pavelko
  • 1,555
  • 2
  • 15
  • 18
1

I've come across two solutions for this issue. The first is Roman's (moving the attributes up in the class hierarchy). The second is to not use attributes for validation that requires information from multiple classes. Here's how that looks:

public class MyController : ApiController {
  public IHttpActionResult Post(Class1 class1) {
    IHttpActionResult actionResult = ValidateSchema(class1);
    if(actionResult != null) {
      return actionResult;
    }
    //Do business stuff here
    return Ok();
  }

  private IHttpActionResult ValidateSchema(Class1 class1) {
    if(class1.ImportantInformation) {
      if(class1.A.X == null) {
        ModelState.AddModelError("A.X", "X is invalid");
      }
      if(class1.B.Y == null) {
        ModelState.AddModelError("B.Y", "Y is invalid");
      }
    }

    if(!ModelState.IsValid) {
      return BadRequest(ModelState);
    }
  }
}
Benjamin U.
  • 580
  • 1
  • 3
  • 16