일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- identityserver3
- slow in the application
- IdentityServer4
- .net
- execution plan
- TPL
- fast in ssms
- C#
- 실행계획 원리
- await
- async await
- Dataannotation
- english
- esl
- MSSQL
- SSMS
- oauth2
- 쿼리 최적화
- validation
- 저장프로시저
- SQLServer
- identityserver
- task
- ThreadPool
- query
- 영어공부
- async
- stored procedure
- SQL Server Optimizer
- 느린 저장프로시저
- Today
- Total
Genius DM
DataAnnotation > Recursive validation for collection items 본문
Attribute 로 리스트 객체 유효성 검사하기
Annotation 을 통한 Validation 은 int, string 등의 데이터 타입 단위의 유효성 검증에 유용하다고 첫 번째 포스트에서 밝혔다. 주로 MVC 에서 많이 사용하므로, 사용자 Form 에서 입력받는 데이터 검증이 그 주된 사용처이기 때문에 리스트 아이템이나 객체 단위로 검증하는 경우에는 사용되는 경우를 아직 보지 못 한 것 같다.
개인적으로 Annotation 을 통한 Class decoration 은 장점이 많다고 생각한다. Model 에 Validation 대상을 명확하게 지정할 수 있으며 Required, Range, Email 처럼 코드 상에서 그 의미도 매우 명확하여 개발자로 하여금 Model 만 보고도 주요 데이터에 대한 파악을 쉽게 할 수 있도록 돕는다. 그리고 무엇보다 Annotation Decorated 라는 표현 처럼, Model 을 예쁘게 꾸민다.
이 장점을 그대로 살려서 Model 내부에 멤버 변수로 선언된 다른 Model 에 대한 검사, 그리고 그것이 리스트라면 리스트 아이템 갯수 만큼 모두 검사할 수 있다면 DataAnnotation 시리즈에서 목표로 하는 " 모든 요소에 대한 벨리데이션 수행 " 목표를 달성할 수 있다.
일단 ValidationResult 를 리턴 받는 이 방식에서 ValidationResult 만으로는 Graph, 즉 계층구조를 구성할 수 없다. ValidationResult 내부에 선언된 IEnumerable<ValidationResult> 형태에 멤버 선언이 없기 때문이다. 준비물은 간략하게 아래와 같다.
- GeniusDMValidationResult : ValidationResult 확장 클래스
확장 클래스에서 내부 ValidationResult List 를 멤버변수로 추가한다. - CheckChildrenAttribute : ValidationAttribute base 클래스 확장 Attribute
싱글 객체와 리스트 객체를 IsValid 컨텍스트에서 계층 구조로 처리 할 클래스 - 샘플 모델
모델 예제
public class Company { [Required] public string Name { get; set; } [Required] public string Address { get; set; }
// CheckChildren 을 통해 내부에 선언된 리스트 객체를 검사한다. 물론 단일 객체에 대한 검사도 가능.
[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
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
public class GeniusDMValidationResult : ValidationResult { public GeniusDMValidationResult() : base("") { } public IList<ValidationResult> NestedResults { get; set; } }
테스트
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(); } }
결과 Sample
- 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
.
.
.
.
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.
- GeniusDMValidationResult : inherits the original ValidationResult class.
In this derived class, declare a ValidationResult list as a member variable. - CheckChildrenAttribute : inherits ValidationAttribute base class.
This is going to process a single object and list object and return it as a graph. - Sample model for you to test.
Sample models
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
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
public class GeniusDMValidationResult : ValidationResult { public GeniusDMValidationResult() : base("") { } public IList<ValidationResult> NestedResults { get; set; } }
Test
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
- 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
.
.
.
.
'.NET' 카테고리의 다른 글
Generics > Covariance and Contravariance (0) | 2018.07.14 |
---|---|
IdentityServer > Storing an access token in IdentityServer3 (0) | 2018.07.08 |
DataAnnotation > Make a custom attribute (0) | 2017.12.22 |
DataAnnotation > DataAnnotation Validation and IValidatableObject (0) | 2017.12.18 |
No assembly found containing an OwinStartupAttribute. (0) | 2017.11.29 |