मैं IValidatableObject का उपयोग कैसे करूं?


182

मैं समझता हूं कि IValidatableObjectएक वस्तु को एक तरह से मान्य करने के लिए उपयोग किया जाता है जो किसी को एक दूसरे के खिलाफ गुणों की तुलना करने देता है।

मैं अभी भी व्यक्तिगत गुणों को मान्य करने के लिए विशेषता रखना चाहता हूं, लेकिन मैं कुछ मामलों में कुछ गुणों पर विफलताओं को अनदेखा करना चाहता हूं।

क्या मैं नीचे के मामले में गलत तरीके से इसका उपयोग करने की कोशिश कर रहा हूं? यदि नहीं तो मैं इसे कैसे लागू करूं?

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.
             */ 
        }
    }
}

जवाबों:


168

सबसे पहले, मुझे सही संसाधनों की ओर इशारा करने के लिए @ paper1337 के लिए धन्यवाद ... मैं पंजीकृत नहीं हूं इसलिए मैं उसे वोट नहीं दे सकता, कृपया ऐसा करें यदि कोई और इसे पढ़ता है।

यहाँ बताया गया है कि मैं क्या करने की कोशिश कर रहा था।

मान्य वर्ग:

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()यदि विफल सत्यापन हैं, तो परिणाम संग्रह में इसका उपयोग किया जाएगा। यदि एक असफल मान्यता नहीं है, तो परिणाम संग्रह में कुछ भी नहीं जोड़ा जाएगा जो कि सफलता का संकेत है।

सत्यापन करना:

    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);
    }

validateAllPropertiesइस विधि को काम करने के लिए गलत पर सेट करना महत्वपूर्ण है । जब validateAllPropertiesझूठी है केवल एक [Required]विशेषता वाले गुण जांचे जाते हैं। यह IValidatableObject.Validate()विधि को सशर्त मान्यताओं को संभालने की अनुमति देता है ।


मैं ऐसे परिदृश्य के बारे में नहीं सोच सकता जहां मैं इसका उपयोग करूंगा। क्या आप मुझे इसका उदाहरण दे सकते हैं कि आप इसका उपयोग कहां करेंगे?
स्टीफन वासिलजेविक

यदि आपके टेबल में ट्रैकिंग कॉलम हैं (जैसे कि इसे बनाने वाले उपयोगकर्ता)। डेटाबेस में इसकी आवश्यकता होती है लेकिन आप इसे बचाने के लिए संदर्भ में SaveChanges में कदम रखते हैं (इसे स्पष्ट रूप से सेट करने के लिए डेवलपर्स की आवश्यकता को याद रखना)। आप निश्चित रूप से बचत करने से पहले मान्य होंगे। इसलिए आप "निर्माता" कॉलम को आवश्यक के रूप में चिह्नित नहीं करते हैं, लेकिन अन्य सभी कॉलम / संपत्तियों के खिलाफ मान्य हैं।
मेटलपॉनिक्स

इस समाधान के साथ समस्या यह है कि अब आप कॉलर पर निर्भर करते हैं कि आपकी वस्तु ठीक से मान्य है या नहीं।
cocogza

इस उत्तर को बढ़ाने के लिए, सभी गुणों को पहचानने के लिए प्रतिबिंब का उपयोग किया जा सकता है, जिसमें सत्यापन गुण हैं, फिर TryValidateProperty को कॉल करें।
पॉल चेरोच

78

Validator के साथ Validation वस्तुओं और गुणों पर जेफ हैंडले के ब्लॉग पोस्ट के उद्धरण :

किसी ऑब्जेक्ट को मान्य करते समय, निम्नलिखित प्रक्रिया Validator.ValidateObject में लागू की जाती है:

  1. संपत्ति-स्तर की विशेषताएँ मान्य करें
  2. यदि कोई सत्यापनकर्ता अमान्य है, तो विफलता लौटाते हुए सत्यापन रद्द करें
  3. ऑब्जेक्ट-स्तरीय विशेषताओं को मान्य करें
  4. यदि कोई सत्यापनकर्ता अमान्य है, तो विफलता लौटाते हुए सत्यापन रद्द करें
  5. यदि डेस्कटॉप फ्रेमवर्क और ऑब्जेक्ट IValiditableObject पर लागू होता है, तो इसकी वैधता विधि को कॉल करें और किसी भी विफलता (ओं) को वापस करें

यह इंगित करता है कि आप जो करने का प्रयास कर रहे हैं वह आउट-ऑफ-द-बॉक्स काम नहीं करेगा क्योंकि सत्यापन चरण # 2 पर समाप्त हो जाएगा। आप उन विशेषताओं को बनाने का प्रयास कर सकते हैं जो अंतर्निर्मित से प्राप्त होते हैं और विशेष रूप से उनकी सामान्य मान्यता का प्रदर्शन करने से पहले एक सक्षम संपत्ति (एक इंटरफ़ेस के माध्यम से) की उपस्थिति के लिए जांच करते हैं। वैकल्पिक रूप से, आप इकाई को Validateविधि में मान्य करने के लिए सभी तर्क रख सकते हैं ।


36

बस कुछ बिंदुओं को जोड़ने के लिए:

क्योंकि Validate()विधि हस्ताक्षर लौटाता है IEnumerable<>, जिसका yield returnउपयोग आलसी परिणाम उत्पन्न करने के लिए किया जा सकता है - यह लाभकारी है यदि कुछ सत्यापन जाँच IO या CPU गहन हैं।

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

इसके अलावा, यदि आप उपयोग कर रहे हैं, तो आप MVC ModelStateसत्यापन परिणाम विफलताओं को ModelStateप्रविष्टियों में परिवर्तित कर सकते हैं (यह उपयोगी हो सकता है यदि आप कस्टम मॉडल बाइंडर में सत्यापन कर रहे हैं ):

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)));
}

अच्छा है! क्या यह मान्य विधि में विशेषताओं और प्रतिबिंब का उपयोग करने योग्य है?
स्काल्क

4

मैंने सत्यापन के लिए एक सामान्य उपयोग सार वर्ग लागू किया

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
मुझे नामांकित मापदंडों का उपयोग करने की शैली पसंद है, यह कोड को पढ़ने में बहुत आसान बनाता है।
ड्रिकिन

0

स्वीकृत उत्तर के साथ समस्या यह है कि यह अब कॉल करने वाले पर निर्भर करता है कि वस्तु ठीक से मान्य है या नहीं। मैं या तो RangeAttribute को हटा दूंगा और Validate विधि के अंदर श्रेणी सत्यापन करूँगा या फिर मैं एक कस्टम विशेषता उप-श्रेणी का निर्माण करूँगा, जो उस श्रेणी में एक तर्क के रूप में आवश्यक संपत्ति का नाम लेता है।

उदाहरण के लिए:

[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

मुझे कॉल करने वाले का जवाब पसंद आया, सिवाय इसके कि कॉलिंग बेस .sValid के परिणामस्वरूप एक स्टैक ओवरफ़्लो अपवाद हुआ, क्योंकि यह बार-बार IsValid विधि को फिर से दर्ज करेगा। इसलिए मैंने इसे एक विशिष्ट प्रकार के सत्यापन के लिए संशोधित किया, मेरे मामले में यह ई-मेल पते के लिए था।

[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;
    }
}

यह बेहतर काम करता है! यह दुर्घटना नहीं करता है और एक अच्छा त्रुटि संदेश पैदा करता है। आशा है कि यह किसी की मदद करता है!


0

मुझे iValidate के बारे में जो बात पसंद नहीं है, वह यह है कि केवल अन्य सभी सत्यापन के बाद ही इसे चलाया जाए।
इसके अतिरिक्त, कम से कम हमारी साइट में, यह एक सहेजने के प्रयास के दौरान फिर से चलेगा। मैं आपको सुझाव दूंगा कि आप केवल एक फंक्शन बनाएं और उसमें अपने सभी वेलिडेशन कोड रखें। वैकल्पिक रूप से वेबसाइटों के लिए, आपके द्वारा मॉडल बनाए जाने के बाद नियंत्रक में आपका "विशेष" सत्यापन हो सकता है। उदाहरण:

 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
            {
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.