在C#中验证列表

3

我们正在使用DataAnnotations来验证我们的模型。

我们模型的一个非常简化的版本如下:

public class Model
{
    public List<Thing> Things;
}

public class Thing
{
    [Required]
    public string Name {get;set;}
}

现在有趣的一点是,如果我创建一个没有名称的Thing并将其添加到模型中,我本应该期望验证失败,但它通过了(惊讶!)。

var model = new Model ();
var invalidThing = new Thing (); // No name would fail validation
model.Things.Add(invalidThing );

var validationContext = new ValidationContext(model);
var validationResults = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(model, validationContext, validationResults, true);

Assert.False (isValid);  // This fails!

我认为这是因为在验证模型时,它验证每个属性,但不验证属性中的项(如果它是一个集合)。 Things 是一个没有验证的属性,所以它通过了验证(尽管它包含无效的项目)。
我们如何确保验证还验证集合属性中的项?是否有一些现成的验证器可供使用?

在ASP.NET MVC的上下文中,您不需要担心它。默认模型绑定器会在模型绑定时处理所有验证属性,即使是绑定到列表时也是如此。 - Reza Aghaei
1
@RezaAghaei 但是,在进行单元测试时,情况并非如此。我需要一种方法来对行为进行单元测试。MVC使用了什么样的魔法来做到这一点? - mkorman
这是默认模型绑定器的工作原理。在创建每个元素时,分配属性值时,它会验证属性并将验证错误添加到模型状态中。最后,所有属性和对象都会经过验证,模型状态包含了所有的验证错误。 - Reza Aghaei
您可以实现一些机制来执行递归验证,就像您在答案中实现的那样,或者像您可以在这里找到的那样,但是总的来说,我认为您问题中的默认行为并不令人惊讶。假设您在“Model”或“Something<Thing>”中有一个类型为Dictionary<string,Thing>的属性,那么您能否期望“Validator”对该属性执行验证? - Reza Aghaei
3个回答

20

我已经通过创建针对集合的自定义验证器来解决了这个问题,该验证器在每个项目上进行验证。简化后的代码如下:

public class ValidateEachItemAttribute : ValidationAttribute
{
    protected readonly List<ValidationResult> validationResults = new List<ValidationResult>();

    public override bool IsValid(object value)
    {
        var list = value as IEnumerable;
        if (list == null) return true;

        var isValid = true;

        foreach (var item in list)
        {
            var validationContext = new ValidationContext(item);
            var isItemValid = Validator.TryValidateObject(item, validationContext, validationResults, true);
            isValid &= isItemValid;
        }
        return isValid;
    }

    // I have ommitted error message formatting
}

现在以这种方式装饰模型将按预期工作:

public class Model
{
    [ValidateEachItem]
    public List<Thing> Things;
}

如果您有多个对象,并使用此代码进行验证,则您的“validationResults”将收集来自_所有_已验证对象的错误(仅创建一次ValidateEachItemAttribute)。我认为应该在foreach之前立即创建此列表。 - smg
2
不再是有效的答案,因为文档中提到:bool IsValid(object) 方法不应该在派生类中被重写。相反,应该实现 IsValid(object, ValidationContext) 方法。 - FilipR

4

如果这是 ASP.NET MVC,另一种选择是在您的模型中实现 IValidatableObject 接口。例如:

public class Model: IValidatableObject
{
    public List<Thing> Things;

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
       //your validation logic here
    }
}

那么在您的控制器中,ModelState.IsValid的结果将取决于Validate方法的实现。当您的模型的多个属性相互依赖时,这将非常有用。


你是如何在这个模型中运行“Thing”类的验证逻辑的?恐怕你的回答并没有解决问题。 - Enrico

2
您的问题中默认行为并不令人意外,我们来描述一下。假设您有一个类型为Dictionary<string, Thing>Something<Thing>的属性,或者在Model中包含Thing对象的一个无类型集合,那么我们如何期望Validator对存储在这些属性中的Thing对象执行验证呢?
由于没有任何关于如何验证这些属性的信息,我们不能期望Validator对存储在这些属性中的Thing对象执行验证。为了验证一个属性,Validator会寻找那个属性的ValidationAttribute,因为它没有找到任何验证属性,所以它不会验证那个属性。
因此,您需要创建一些ValidationAttribute来代替您进行验证,并使用验证属性装饰属性。您可以像在答案中实现的那样实现一些内容。
注意:在ASP.NET MVC上下文中,您不必担心这个问题。默认模型绑定器在模型绑定时处理所有验证属性,即使是绑定到列表。这是默认模型绑定器的工作。在创建每个元素、分配值给属性时,它验证属性并将验证错误添加到模型状态。最后,所有属性和对象都被验证,模型状态包含所有验证错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接