Pertanyaan Bagaimana saya bisa memberi tahu validator Anotasi Data untuk memvalidasi properti anak yang kompleks?


Dapatkah saya secara otomatis memvalidasi objek anak yang kompleks ketika memvalidasi objek induk dan menyertakan hasilnya dalam populasi ICollection<ValidationResult>?

Jika saya menjalankan kode berikut:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ConsoleApplication1
{
    public class Person
    {
        [Required]
        public string Name { get; set; }

        public Address Address { get; set; }
    }

    public class Address
    {
        [Required]
        public string Street { get; set; }

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

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

    class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person
            {
                Name = null,
                Address = new Address
                {
                    Street = "123 Any St",
                    City = "New York",
                    State = null
                }
            };

            var validationContext = new ValidationContext(person, null, null);
            var validationResults = new List<ValidationResult>();

            var isValid = Validator.TryValidateObject(person, validationContext, validationResults);

            Console.WriteLine(isValid);

            validationResults.ForEach(r => Console.WriteLine(r.ErrorMessage));

            Console.ReadKey(true);
        }
    }
}

Saya mendapatkan hasil sebagai berikut:

False
The Name field is required.

Tapi saya mengharapkan sesuatu yang mirip dengan:

False
The Name field is required.
The State field is required.


Saya menawarkan hadiah untuk solusi validasi objek anak yang lebih baik tetapi tidak mendapatkan pengambil, idealnya

  • memvalidasi objek anak ke kedalaman yang sewenang-wenang
  • menangani beberapa kesalahan per objek
  • benar mengidentifikasi kesalahan validasi pada bidang objek anak.

Saya masih terkejut kerangka tidak mendukung ini.


32
2018-03-22 16:10


asal


Jawaban:


Anda harus membuat atribut validator Anda sendiri (misalnya, [CompositeField]) yang memvalidasi properti anak.


5
2018-03-22 16:19



Masalah - Model Urutan Pengikat

Ini, sayangnya, perilaku standar Validator.TryValidateObject yang

tidak secara rekursif memvalidasi nilai properti objek

Sebagaimana ditunjukkan dalam artikel Jeff Handley tentang Memvalidasi Objek dan Properti dengan Validator, secara default, validator akan memvalidasi secara berurutan:

  1. Atribut Tingkat Properti
  2. Atribut Tingkat Obyek
  3. Implementasi Model-Level IValidatableObject

Masalahnya adalah, di setiap langkah dari jalan ...

Jika ada validator yang tidak valid, Validator.ValidateObject akan membatalkan validasi dan mengembalikan kegagalan (s)

Masalah - Model Binder Fields

Masalah lain yang mungkin adalah bahwa pengikat model hanya akan menjalankan validasi pada objek yang telah memutuskan untuk mengikat. Misalnya, jika Anda tidak memberikan masukan untuk bidang dalam jenis kompleks pada model Anda, pengikat model tidak perlu memeriksa properti tersebut sama sekali karena tidak disebut konstruktor pada objek tersebut. Menurut artikel bagus dari Brad Wilson Input Validasi vs Validasi Model di ASP.NET MVC:

Alasan kami tidak "menyelam" ke dalam objek Alamat secara rekursif adalah bahwa tidak ada dalam bentuk yang mengikat setiap nilai di dalam Alamat.

Solusi - Validasi Objek pada saat yang sama dengan Properties

Salah satu cara untuk memecahkan masalah ini adalah dengan mengonversi validasi tingkat objek ke validasi tingkat properti dengan menambahkan atribut validasi kustom ke properti yang akan kembali dengan hasil validasi objek itu sendiri.

Artikel Josh Carroll Validasi Rekursif Menggunakan DataAnnotations menyediakan implementasi dari satu strategi tersebut (awalnya di pertanyaan SO ini). Jika kita ingin memvalidasi tipe kompleks (seperti Alamat), kita dapat menambahkan kebiasaan ValidateObject atribut ke properti, sehingga dievaluasi pada langkah pertama

public class Person {
  [Required]
  public String Name { get; set; }

  [Required, ValidateObject]
  public Address Address { get; set; }
}

Anda harus menambahkan yang berikut ini ValidateObjectAttribute pelaksanaan:

public class ValidateObjectAttribute: ValidationAttribute {
   protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
      var results = new List<ValidationResult>();
      var context = new ValidationContext(value, null, null);

      Validator.TryValidateObject(value, context, results, true);

      if (results.Count != 0) {
         var compositeResults = new CompositeValidationResult(String.Format("Validation for {0} failed!", validationContext.DisplayName));
         results.ForEach(compositeResults.AddResult);

         return compositeResults;
      }

      return ValidationResult.Success;
   }
}

public class CompositeValidationResult: ValidationResult {
   private readonly List<ValidationResult> _results = new List<ValidationResult>();

   public IEnumerable<ValidationResult> Results {
      get {
         return _results;
      }
   }

   public CompositeValidationResult(string errorMessage) : base(errorMessage) {}
   public CompositeValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames) {}
   protected CompositeValidationResult(ValidationResult validationResult) : base(validationResult) {}

   public void AddResult(ValidationResult validationResult) {
      _results.Add(validationResult);
   }
}

Solusi - Validasi Model pada waktu yang Sama sebagai Properties

Untuk objek yang menerapkan IValidatableObject, ketika kami memeriksa ModelState, kami juga dapat memeriksa untuk melihat apakah model itu sendiri valid sebelum mengembalikan daftar kesalahan. Kami dapat menambahkan kesalahan apa pun yang kami inginkan dengan menelepon ModelState.AddModelError(bidang, kesalahan). Sebagaimana ditentukan dalam Cara memaksa MVC untuk memvalidasi IValidatableObject, kita bisa melakukannya seperti ini:

[HttpPost]
public ActionResult Create(Model model) {
    if (!ModelState.IsValid) {
        var errors = model.Validate(new ValidationContext(model, null, null));
        foreach (var error in errors)                                 
            foreach (var memberName in error.MemberNames)
                ModelState.AddModelError(memberName, error.ErrorMessage);

        return View(post);
    }
}

Juga, jika Anda menginginkan solusi yang lebih elegan, Anda dapat menulis kode sekali dengan memberikan implementasi pengimplementasi model kustom Anda sendiri di Application_Start () dengan ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());. Ada implementasi yang bagus sini dan sini


23
2018-02-10 14:10



Saya juga berlari ke ini, dan menemukan utas ini. Ini tiket pertama:

namespace Foo
{
    using System.ComponentModel.DataAnnotations;
    using System.Linq;

    /// <summary>
    /// Attribute class used to validate child properties.
    /// </summary>
    /// <remarks>
    /// See: http://stackoverflow.com/questions/2493800/how-can-i-tell-the-data-annotations-validator-to-also-validate-complex-child-pro
    /// Apparently the Data Annotations validator does not validate complex child properties.
    /// To do so, slap this attribute on a your property (probably a nested view model) 
    /// whose type has validation attributes on its properties.
    /// This will validate until a nested <see cref="System.ComponentModel.DataAnnotations.ValidationAttribute" /> 
    /// fails. The failed validation result will be returned. In other words, it will fail one at a time. 
    /// </remarks>
    public class HasNestedValidationAttribute : ValidationAttribute
    {
        /// <summary>
        /// Validates the specified value with respect to the current validation attribute.
        /// </summary>
        /// <param name="value">The value to validate.</param>
        /// <param name="validationContext">The context information about the validation operation.</param>
        /// <returns>
        /// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult"/> class.
        /// </returns>
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var isValid = true;
            var result = ValidationResult.Success;

            var nestedValidationProperties = value.GetType().GetProperties()
                .Where(p => IsDefined(p, typeof(ValidationAttribute)))
                .OrderBy(p => p.Name);//Not the best order, but at least known and repeatable.

            foreach (var property in nestedValidationProperties)
            {
                var validators = GetCustomAttributes(property, typeof(ValidationAttribute)) as ValidationAttribute[];

                if (validators == null || validators.Length == 0) continue;

                foreach (var validator in validators)
                {
                    var propertyValue = property.GetValue(value, null);

                    result = validator.GetValidationResult(propertyValue, new ValidationContext(value, null, null));
                    if (result == ValidationResult.Success) continue;

                    isValid = false;
                    break;
                }

                if (!isValid)
                {
                    break;
                }
            }
            return result;
        }
    }
}

8
2018-02-24 21:07