IValidatableObject'i nasıl kullanırım?


182

Bunun IValidatableObjectbir nesneyi özellikleri birbirleriyle karşılaştırmasını sağlayacak şekilde doğrulamak için kullanıldığını anlıyorum .

Yine de bireysel özellikleri doğrulamak için niteliklere sahip olmak istiyorum, ancak bazı durumlarda bazı özelliklerde başarısızlıkları yoksaymak istiyorum.

Aşağıdaki durumda yanlış mı kullanmaya çalışıyorum? Değilse, bunu nasıl uygularım?

public class ValidateMe : IValidatableObject
{
    [Required]
    public bool Enable { get; set; }

    [Range(1, 5)]
    public int Prop1 { get; set; }

    [Range(1, 5)]
    public int Prop2 { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!this.Enable)
        {
            /* Return valid result here.
             * I don't care if Prop1 and Prop2 are out of range
             * if the whole object is not "enabled"
             */
        }
        else
        {
            /* Check if Prop1 and Prop2 meet their range requirements here
             * and return accordingly.
             */ 
        }
    }
}

Yanıtlar:


168

Öncelikle, @ paper1337 sayesinde beni doğru kaynaklara yönlendirdiği için ... Kayıtlı değilim, bu yüzden ona oy veremiyorum, lütfen bunu başka biri okursa yapın.

İşte yapmaya çalıştığım şeyi nasıl başaracağım.

Doğrulanabilir sınıf:

public class ValidateMe : IValidatableObject
{
    [Required]
    public bool Enable { get; set; }

    [Range(1, 5)]
    public int Prop1 { get; set; }

    [Range(1, 5)]
    public int Prop2 { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var results = new List<ValidationResult>();
        if (this.Enable)
        {
            Validator.TryValidateProperty(this.Prop1,
                new ValidationContext(this, null, null) { MemberName = "Prop1" },
                results);
            Validator.TryValidateProperty(this.Prop2,
                new ValidationContext(this, null, null) { MemberName = "Prop2" },
                results);

            // some other random test
            if (this.Prop1 > this.Prop2)
            {
                results.Add(new ValidationResult("Prop1 must be larger than Prop2"));
            }
        }
        return results;
    }
}

Validator.TryValidateProperty()Başarısız doğrulamalar kullanılıyorsa , kullanmak sonuç koleksiyonuna eklenir. Başarısız bir doğrulama yoksa, sonuç koleksiyonuna bir başarı göstergesi olan hiçbir şey eklenmeyecektir.

Doğrulamanın yapılması:

    public void DoValidation()
    {
        var toValidate = new ValidateMe()
        {
            Enable = true,
            Prop1 = 1,
            Prop2 = 2
        };

        bool validateAllProperties = false;

        var results = new List<ValidationResult>();

        bool isValid = Validator.TryValidateObject(
            toValidate,
            new ValidationContext(toValidate, null, null),
            results,
            validateAllProperties);
    }

validateAllPropertiesBu yöntemin çalışması için false değerine ayarlanması önemlidir . validateAllPropertiesFalse olduğunda , yalnızca [Required]özniteliği olan özellikler denetlenir. Bu, IValidatableObject.Validate()yöntemin koşullu doğrulamaları işlemesine izin verir .


Bunu kullanacağım bir senaryo düşünemiyorum. Bunu nerede kullanacağınıza dair bir örnek verebilir misiniz?
Stefan Vasiljevic

Tablonuzda izleme sütunları varsa (onu oluşturan kullanıcı gibi). Veritabanında gereklidir, ancak onu doldurmak için bağlamda SaveChanges'e girersiniz (geliştiricilerin açıkça ayarlamayı hatırlama gereğini ortadan kaldırır). Tabii ki, kaydetmeden önce doğrularsınız. Dolayısıyla, "yaratıcı" sütununu gerektiği gibi işaretlemez, ancak diğer tüm sütunlara / özelliklere göre doğrularsınız.
MetalPhoenix

Bu çözümün sorunu, şimdi nesnenizin düzgün bir şekilde doğrulanması için arayana bağımlı olmanızdır.
cocogza

Bu yanıtı geliştirmek için, doğrulama özniteliklerine sahip tüm özellikleri bulmak için yansıma kullanılabilir, ardından TryValidateProperty'yi çağırın.
Paul Chernoch

78

Validator ile Doğrulama Nesneleri ve Özellikleri ile ilgili Jeff Handley'in Blog Yazısından Alıntı :

Bir nesneyi doğrularken, Validator.ValidateObject içinde aşağıdaki işlem uygulanır:

  1. Mülk düzeyindeki özellikleri doğrulayın
  2. Doğrulayıcılardan herhangi biri geçersizse, hata (lar) ı döndüren doğrulamayı iptal et
  3. Nesne düzeyi niteliklerini doğrulama
  4. Doğrulayıcılardan herhangi biri geçersizse, hata (lar) ı döndüren doğrulamayı iptal et
  5. Masaüstü çerçevesinde ve nesne IValidatableObject uygularsa, Validate yöntemini çağırın ve herhangi bir hata (lar) döndürün

Bu, doğrulama adım 2'de iptal edileceğinden, yapmaya çalıştığınız şeyin kutudan çıkmayacağını gösterir. Yerleşik olanlardan devralınan öznitelikler oluşturmayı deneyebilir ve normal doğrulamalarını gerçekleştirmeden önce özellikle etkin bir özelliğin (arabirim aracılığıyla) varlığını kontrol edebilirsiniz. Alternatif olarak, Validateyöntemi doğrulamak için tüm mantığı koyabilirsiniz .


36

Birkaç nokta eklemek için:

Çünkü Validate()yöntem imzası döner IEnumerable<>, yield returnlazily sonuçları üretmek için kullanılabilir - bu faydalıdır doğrulama kontrolleri bazı ES veya işlemci yoğun ise.

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    if (this.Enable)
    {
        // ...
        if (this.Prop1 > this.Prop2)
        {
            yield return new ValidationResult("Prop1 must be larger than Prop2");
        }

Ayrıca, MVC ModelStatedoğrulama sonucu hatalarını ModelStategirişlere aşağıdaki gibi dönüştürebilirsiniz ( özel bir model bağlayıcısında doğrulama yapıyorsanız bu yararlı olabilir ):

var resultsGroupedByMembers = validationResults
    .SelectMany(vr => vr.MemberNames
                        .Select(mn => new { MemberName = mn ?? "", 
                                            Error = vr.ErrorMessage }))
    .GroupBy(x => x.MemberName);

foreach (var member in resultsGroupedByMembers)
{
    ModelState.AddModelError(
        member.Key,
        string.Join(". ", member.Select(m => m.Error)));
}

Güzel bir! Validate yönteminde nitelikleri ve yansımayı kullanmaya değer mi?
Schalk

4

Doğrulama için bir genel kullanım soyut sınıfı uyguladım

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

namespace App.Abstractions
{
    [Serializable]
    abstract public class AEntity
    {
        public int Id { get; set; }

        public IEnumerable<ValidationResult> Validate()
        {
            var vResults = new List<ValidationResult>();

            var vc = new ValidationContext(
                instance: this,
                serviceProvider: null,
                items: null);

            var isValid = Validator.TryValidateObject(
                instance: vc.ObjectInstance,
                validationContext: vc,
                validationResults: vResults,
                validateAllProperties: true);

            /*
            if (true)
            {
                yield return new ValidationResult("Custom Validation","A Property Name string (optional)");
            }
            */

            if (!isValid)
            {
                foreach (var validationResult in vResults)
                {
                    yield return validationResult;
                }
            }

            yield break;
        }


    }
}

1
Adlandırılmış parametreleri kullanma tarzını seviyorum, kodu okumayı çok daha kolay hale getiriyor.
drizin

0

Kabul edilen cevapla ilgili sorun, artık nesnenin doğru şekilde doğrulanması için arayana bağlı olmasıdır. Ben ya RangeAttribute kaldırmak ve Validate yöntemi içinde aralık doğrulaması yapmak veya yapıcı üzerinde bir argüman olarak gerekli özelliğin adını alır özel özellik alt sınıflama RangeAttribute oluşturur.

Örneğin:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
class RangeIfTrueAttribute : RangeAttribute
{
    private readonly string _NameOfBoolProp;

    public RangeIfTrueAttribute(string nameOfBoolProp, int min, int max) : base(min, max)
    {
        _NameOfBoolProp = nameOfBoolProp;
    }

    public RangeIfTrueAttribute(string nameOfBoolProp, double min, double max) : base(min, max)
    {
        _NameOfBoolProp = nameOfBoolProp;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectType.GetProperty(_NameOfBoolProp);
        if (property == null)
            return new ValidationResult($"{_NameOfBoolProp} not found");

        var boolVal = property.GetValue(validationContext.ObjectInstance, null);

        if (boolVal == null || boolVal.GetType() != typeof(bool))
            return new ValidationResult($"{_NameOfBoolProp} not boolean");

        if ((bool)boolVal)
        {
            return base.IsValid(value, validationContext);
        }
        return null;
    }
}

0

Bu çağrı tabanı dışında cocogza'nın cevabını beğendim.IsValid , IsValid yöntemini tekrar tekrar gireceği için yığın taşması istisnasıyla sonuçlandı. Bu yüzden belirli bir doğrulama türü için değiştirdim, benim durumumda bir e-posta adresi içindi.

[AttributeUsage(AttributeTargets.Property)]
class ValidEmailAddressIfTrueAttribute : ValidationAttribute
{
    private readonly string _nameOfBoolProp;

    public ValidEmailAddressIfTrueAttribute(string nameOfBoolProp)
    {
        _nameOfBoolProp = nameOfBoolProp;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (validationContext == null)
        {
            return null;
        }

        var property = validationContext.ObjectType.GetProperty(_nameOfBoolProp);
        if (property == null)
        {
            return new ValidationResult($"{_nameOfBoolProp} not found");
        }

        var boolVal = property.GetValue(validationContext.ObjectInstance, null);

        if (boolVal == null || boolVal.GetType() != typeof(bool))
        {
            return new ValidationResult($"{_nameOfBoolProp} not boolean");
        }

        if ((bool)boolVal)
        {
            var attribute = new EmailAddressAttribute {ErrorMessage = $"{value} is not a valid e-mail address."};
            return attribute.GetValidationResult(value, validationContext);
        }
        return null;
    }
}

Bu çok daha iyi çalışıyor! Kilitlenmez ve hoş bir hata mesajı verir. Umarım bu birine yardımcı olur!


0

İValidate hakkında sevmediğim şey, sadece diğer tüm doğrulamalardan sonra çalışıyor gibi görünüyor.
Ayrıca, en azından sitemizde, bir kaydetme girişimi sırasında tekrar çalışır. Basitçe bir işlev oluşturmanızı ve tüm doğrulama kodunuzu buna yerleştirmenizi öneririm. Alternatif olarak web siteleri için, model oluşturulduktan sonra denetleyicide "özel" doğrulamanız olabilir. Misal:

 public ActionResult Update([DataSourceRequest] DataSourceRequest request, [Bind(Exclude = "Terminal")] Driver driver)
    {

        if (db.Drivers.Where(m => m.IDNumber == driver.IDNumber && m.ID != driver.ID).Any())
        {
            ModelState.AddModelError("Update", string.Format("ID # '{0}' is already in use", driver.IDNumber));
        }
        if (db.Drivers.Where(d => d.CarrierID == driver.CarrierID
                                && d.FirstName.Equals(driver.FirstName, StringComparison.CurrentCultureIgnoreCase)
                                && d.LastName.Equals(driver.LastName, StringComparison.CurrentCultureIgnoreCase)
                                && (driver.ID == 0 || d.ID != driver.ID)).Any())
        {
            ModelState.AddModelError("Update", "Driver already exists for this carrier");
        }

        if (ModelState.IsValid)
        {
            try
            {
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.