shyaway

DataAnnotation > Recursive validation for collection items 본문

.NET

DataAnnotation > Recursive validation for collection items

shyaway 2017. 12. 29. 23:13

Validation for collection items with attribute class


I mentioned that validation via annotation attribute is great for data types such as int, string, and etc in the first post. This is most likely useful in MVC app, hence validation will take care of user input data. That's why I haven't seen an annotation attribute validating list items or objects yet, IMAO. 


I think data annotations are great for decorating a class. It clearly shows developers that what's going to be validated and what's not like Required, Range, and email. This certainly helps them to see which data is important or not by just taking a glance at the model. And annotations literally decorates a model gracefully. 


Validating all properties, even if it's an object or list items, in a model is what DataAnnotation posts are for. We can achieve this goal, taking the full advantage of annotation attribute to check nested items in a model if you implement these below.


First of all, we need Graph data structure. But ValidationResult cannot produce it because it doesn't have any list member in it like IEnumerable<ValidationResult>. You need a nested list to build a hierarchical structure. See prerequisites below.


    1. GeniusDMValidationResult : inherits the original ValidationResult class.
      In this derived class, declare a ValidationResult list as a member variable.

    2. CheckChildrenAttribute : inherits ValidationAttribute base class.
      This is going to process a single object and list object and return it as a graph.

    3. Sample model for you to test.


Sample models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Company
{
  [Required]
  public string Name { get; set; }
  [Required]
  public string Address { get; set; }
        // CheckChildren : checks all items if it is an object, checks an item.        
  [CheckChildren]
  public List<Department> Departments { get; set; }
}
public class Department
{
  [Required]
  public string Name { get; set; }
  [Required]
  public int EmployeeCount { get; set; }
  [CheckChildren]
  public List<Employee> Employees { get; set; }
}
public class Employee
{
  [Required]
  public string Name { get; set; }
  public int Age { get; set; }
  public int Sex { get; set; }
}

CheckChildrenAttribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class CheckChildrenAttribute : ValidationAttribute
{       
  protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  {
    GeniusDMValidationResult result = new GeniusDMValidationResult();
    result.ErrorMessage = string.Format(@"Error occured at {0}", validationContext.DisplayName);           
 
    IEnumerable list = value as IEnumerable;
    if (list == null)
    {
      // Single Object
      List<ValidationResult> results = new List<ValidationResult>();
      Validator.TryValidateObject(value, validationContext, results, true);
      result.NestedResults = results;
 
      return result;
    }
    else
    {
      List<ValidationResult> recursiveResultList = new List<ValidationResult>();
 
      // List Object
      foreach (var item in list)
      {
        List<ValidationResult> nestedItemResult = new List<ValidationResult>();
        ValidationContext context = new ValidationContext(item, validationContext.ServiceContainer, null);
 
        GeniusDMValidationResult nestedParentResult = new GeniusDMValidationResult();
        nestedParentResult.ErrorMessage = string.Format(@"Error occured at {0}", validationContext.DisplayName);
 
        Validator.TryValidateObject(item, context, nestedItemResult, true);
        nestedParentResult.NestedResults = nestedItemResult;
        recursiveResultList.Add(nestedParentResult);
      }
 
      result.NestedResults = recursiveResultList;
      return result;
    }
  }
}


GeniusDMValidationResult

1
2
3
4
5
6
7
8
9
public class GeniusDMValidationResult : ValidationResult
{
  public GeniusDMValidationResult() : base("")
  {
     
  }
 
  public IList<ValidationResult> NestedResults { get; set; }
}


Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Program
{
  static void Main(string[] args)
  {
    Company company = new Company();
    company.Departments = new List<Department> {
      new Department {
        Employees = new List<Employee> {
          new Employee(),
          new Employee()
        }
      },
      new Department {
        Employees = new List<Employee> {
          new Employee(),
          new Employee()
        }
      },
      new Department {
        Employees = new List<Employee> {
          new Employee(),
          new Employee()
        }
      }
    };
     
    List<ValidationResult> results = new List<ValidationResult>();
    ValidationContext context = new ValidationContext(company, null, null);
 
    Validator.TryValidateObject(company, context, results, true);
    Console.Read();
  }
}

Result Sample

If you inspect the result above, it's going to look like this.
    • results = count 3
      • [0] Name field is required
      • [1] Address field is required
      • [2] Departments has errors -> NestedResult = count 3
        • [0] Error occured at Departments -> NestedResult = count 2
          • [0] Name field is required
          • [1] Error occured at Employees -> NestedResult = count 2
            • [0] Name field is required
              .
              .
              .
              .
If you saw my second post of [DataAnnotation] and made a utility and implemented IValidatableObject, the additional results will be come after those results above on 1 depth position. If you've followed everything, now you can see all validation results and build your own business logics accordingly. 

It doesn't look seamless because the Codehighlighter didn't work well and different fonts are here and there... but hope somebody loves it. Thanks.




















Comments