ฉันจะใช้ 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()วิธีการจัดการการตรวจสอบตามเงื่อนไข


ฉันไม่สามารถคิดถึงสถานการณ์ที่ฉันจะใช้สิ่งนี้ คุณช่วยยกตัวอย่างที่คุณจะใช้สิ่งนี้ได้หรือไม่?
สเตฟาน Vasiljevic

หากคุณมีคอลัมน์ติดตามในตารางของคุณ (เช่นผู้ใช้ที่สร้างมัน) จำเป็นต้องมีในฐานข้อมูล แต่คุณเข้าสู่ขั้นตอนในบันทึกการเปลี่ยนแปลงในบริบทเพื่อเติมข้อมูล (ไม่จำเป็นต้องให้ผู้พัฒนาจำเพื่อตั้งค่าไว้อย่างชัดเจน) แน่นอนคุณจะตรวจสอบก่อนบันทึก ดังนั้นคุณจะไม่ทำเครื่องหมายคอลัมน์ "ผู้สร้าง" ตามต้องการ แต่ตรวจสอบความถูกต้องกับคอลัมน์ / คุณสมบัติอื่น ๆ ทั้งหมด
MetalPhoenix

ปัญหาเกี่ยวกับการแก้ปัญหานี้คือตอนนี้คุณขึ้นอยู่กับผู้โทรสำหรับวัตถุของคุณที่จะตรวจสอบความถูกต้อง
cocogza

เพื่อปรับปรุงคำตอบนี้สามารถใช้การสะท้อนเพื่อค้นหาคุณสมบัติทั้งหมดที่มีคุณสมบัติการตรวจสอบแล้วเรียก TryValidateProperty
Paul Chernoch

78

อ้างอิงจากบล็อกโพสต์ของ Jeff Handley เกี่ยวกับการตรวจสอบวัตถุและคุณสมบัติด้วยเครื่องมือตรวจสอบ :

เมื่อตรวจสอบวัตถุกระบวนการต่อไปนี้จะถูกนำไปใช้ใน Validator.ValidateObject:

  1. ตรวจสอบแอตทริบิวต์ของคุณสมบัติระดับ
  2. หากตัวตรวจสอบความถูกต้องไม่ถูกต้องให้ยกเลิกการตรวจสอบความถูกต้องที่ส่งคืนความล้มเหลว
  3. ตรวจสอบความถูกต้องของแอตทริบิวต์ระดับวัตถุ
  4. หากตัวตรวจสอบความถูกต้องไม่ถูกต้องให้ยกเลิกการตรวจสอบความถูกต้องที่ส่งคืนความล้มเหลว
  5. ถ้าบนเดสก์ท็อปเฟรมเวิร์กและวัตถุใช้ IValidatableObject ให้เรียกเมธอด Validate และส่งคืนความล้มเหลวใด ๆ

สิ่งนี้บ่งชี้ว่าสิ่งที่คุณพยายามทำจะไม่ทำงานนอกกรอบเพราะการตรวจสอบจะถูกยกเลิกในขั้นตอนที่ # 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)));
}

ทำได้ดีนี่! มันคุ้มค่าหรือไม่ที่ใช้คุณสมบัติและภาพสะท้อนในวิธีตรวจสอบความถูกต้อง
Schalk

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
ฉันชอบสไตล์ของการใช้พารามิเตอร์ที่มีชื่อทำให้การอ่านรหัสง่ายขึ้นมาก
drizin

0

ปัญหาของคำตอบที่ยอมรับก็คือตอนนี้มันขึ้นอยู่กับผู้โทรสำหรับวัตถุที่จะตรวจสอบอย่างถูกต้อง ฉันจะลบ RangeAttribute และทำการตรวจสอบช่วงภายในวิธีการตรวจสอบหรือฉันจะสร้างแอตทริบิวต์ Subclassing กำหนดเองคลาส RangeAttribute ที่ใช้ชื่อของคุณสมบัติที่ต้องการเป็นอาร์กิวเมนต์ในตัวสร้าง

ตัวอย่างเช่น:

[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

ฉันชอบคำตอบของ cocogzaยกเว้นการเรียก base.IsValid ส่งผลให้เกิดข้อยกเว้นสแต็กล้นเนื่องจากจะเข้าสู่วิธี 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
            {
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.