[ASP .NET MVC 5] 入れ子のModelのValidationを行う

ASP .NET MVC 5で確認。

入れ子のModelのValidationは効かない。
Attachmentの中のNameAndExtensionとかのRequiredはValidationされない。

```
    public class Email
    {
        [Required]
        public string From { get; set; }

        [Required]
        public string Subject { get; set; }

        public string Body { get; set; }

        [Required]
        public EmailAttachment Attachment { get; set; }


        public Email()
        {
        }

        public class EmailAttachment
        {
            [Required]
            public string NameAndExtension { get; set; }

            [Required]
            public byte[] File { get; set; }
        }
    }
```

子のValidationを行うためのAttributeを作成する

```
    /// <summary>
    /// 子オブジェクトのValidationも行います
    /// </summary>
    public class NestedValidateAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (value == null) return ValidationResult.Success;
            var results = new List<ValidationResult>();
            var context = new ValidationContext(value, null, null);
            //Validationを実施
            Validator.TryValidateObject(value, context, results, true);

            if (results.Count != 0)
            {
                var validationResults = results.Select(x => new ValidationResult(x.ErrorMessage, x.MemberNames.Select(y => $"{validationContext.DisplayName}.{y}")));
                var compositeResults = new CompositeValidationResult(String.Format("Validation for {0} failed!", new { validationContext.DisplayName }));
                compositeResults.AddResults(validationResults);
                return compositeResults;

            }

            return ValidationResult.Success;
        }
    }
    /// <summary>
    /// 複数のValidation結果を格納できる<see cref="ValidationResult" />
    /// </summary>
    public class CompositeValidationResult : ValidationResult
    {
        /// <summary>
        /// 複数のValidation結果
        /// </summary>
        public IEnumerable<ValidationResult> Results { get;} = new List<ValidationResult>();

        public CompositeValidationResult(string errorMessage) : base(errorMessage) { }
        public CompositeValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames) { }
        protected CompositeValidationResult(ValidationResult validationResult) : base(validationResult) { }
        /// <summary>
        /// エラーを登録
        /// </summary>
        /// <param name="validationResult"></param>
        public void AddResult(ValidationResult validationResult)
        {
            (Results as IList<ValidationResult>).Add(validationResult);
        }
        /// <summary>
        /// エラーを登録
        /// </summary>
        /// <param name="validationResults"></param>
        public void AddResults(IEnumerable<ValidationResult> validationResults)
        {
            (Results as List<ValidationResult>).AddRange(validationResults);
        }
    }
```

使うときはこんな感じでAttachmentにNestedValidate属性をつければよい。

```
    public class Email
    {
        [Required]
        public string From { get; set; }

        [Required]
        public string Subject { get; set; }

        public string Body { get; set; }

        [Required]
        [NestedValidate]
        public EmailAttachment Attachment { get; set; }


        public Email()
        {
        }

        public class EmailAttachment
        {
            [Required]
            public string NameAndExtension { get; set; }

            [Required]
            public byte[] File { get; set; }
        }
    }
```

Controller内で確認.
ModelState内にValidation結果が格納されている。

```
        public ActionResult Create(Email viewModel)
        {
            var errors = ModelState
                .Where(x => x.Value.Errors.Count > 0)
                .Select(y =>
                new
                {
                    MemberName = y.Key,
                    ErrorMessages = y.Value.Errors.Select(e => e.ErrorMessage).ToArray(),
                    Exception = y.Value.Errors.Select(e => e.Exception).FirstOrDefault()?.ToString()
                });

/*以下略*/
        }
```

参考：
https://social.msdn.microsoft.com/Forums/vstudio/en-US/4cbf4482-f4f5-489d-bbf1-65810f307d16/data-annontation-validation-with-nested-object?forum=csharpgeneral