ASP.NET MVC:使用DataAnnotation进行自定义验证

112

我有一个具有4个字符串属性的模型。我知道可以使用StringLength注释验证单个属性的长度。然而,我想验证这4个属性的组合长度。

在MVC中,使用数据注释该如何实现呢?

我之所以提出这个问题,是因为我刚学习MVC,希望在自己想出解决方案之前找到正确的方法。


2
你看过Fluent Validation吗?它比Data Annotations更好地处理复杂的情况。 - levelnis
请查看高度推荐的解决方案... http://www.dotnetcurry.com/ShowArticle.aspx?ID=776 - Niks
谢谢回答。我会去看看Fluent Validation,以前没听说过。而且Niks,Darin基本上写出了你发布的链接中文章所解释的内容。所以,谢谢...真是太棒了! - Danny van der Kraan
6个回答

181

你可以编写自定义验证属性:

public class CombinedMinLengthAttribute: ValidationAttribute
{
    public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
    {
        this.PropertyNames = propertyNames;
        this.MinLength = minLength;
    }

    public string[] PropertyNames { get; private set; }
    public int MinLength { get; private set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
        var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
        var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
        if (totalLength < this.MinLength)
        {
            return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
        }
        return null;
    }
}

然后您可能会拥有一个视图模型,并将其属性之一装饰起来:

public class MyViewModel
{
    [CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
    public string Foo { get; set; }
    public string Bar { get; set; }
    public string Baz { get; set; }
}

4
谢谢回复,我已接受您的答案。实际上有点尴尬。您写出了整个解决方案!呵呵。只需要更改IsValid函数以检查最大长度即可。那么这是这些问题的已接受MVC解决方案吗? - Danny van der Kraan
7
@DannyvanderKraan,是的,这是被认可的方法。当然,这种方式非常糟糕,我从不使用它,而是使用FluentValidation.NET来进行验证。 - Darin Dimitrov
11
这里是 http://fluentvalidation.codeplex.com/。你可以为视图模型编写一个简单的验证器,它可能看起来像这样(一行代码):`this.RuleFor(x => x.Foo).Must((x, foo) => x.Foo.Length + x.Bar.Length + x.Baz.Length < 20).WithMessage("The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")。`现在看看我需要你写的带有数据注释的代码,并告诉我哪一个更好。相比声明性验证模型,命令式模型非常优秀。 - Darin Dimitrov
1
这有点晚了,但是有人知道是否需要打开不同的设置才能允许自定义数据注释吗?我知道在web.config文件中添加一个用于非侵入式js的命名空间,但还有其他地方吗? - Jose
1
我一早上都在找这个!我已经实现了它,但不幸的是,当调用IsValid时,validationContext为空。你有什么想法我做错了什么吗? :-( - Grimm The Opiner
显示剩余5条评论

102

自验证模型

你的模型应该实现 IValidatableObject 接口。将你的验证代码放在 Validate 方法中:

public class MyModel : IValidatableObject
{
    public string Title { get; set; }
    public string Description { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Title == null)
            yield return new ValidationResult("*", new [] { nameof(Title) });

        if (Description == null)
            yield return new ValidationResult("*", new [] { nameof(Description) });
    }
}

请注意:这是一种服务器端验证方式。它不适用于客户端,仅在表单提交后才会执行验证。


感谢回答,Andrei。虽然您的解决方案也可以,但我选择Darin的解决方案,因为它更具可重用性。 - Danny van der Kraan
6
yield return new ValidationResult("标题不能为空.", "Title"); 这里添加了属性名,如果需要将验证错误分组显示,则非常有用。 - Mike Kingscott
5
请注意,此验证方法仅在所有验证属性通过验证后才会被调用。 - Pedro
3
这对我很有效,因为我的验证非常具体。添加自定义属性对我来说过于复杂,因为该验证不会被重用。 - Steve S
这就是我要找的。谢谢! - Amol Jadhav

28

ExpressiveAnnotations提供了这样一个可能性:

[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }

太棒了!我的祈祷得到了回应 :) - Korayem
刚刚发现了这个答案,它节省了很多时间。ExpressiveAnnotations 真是太棒了! - Brad

11
为了改进Darin的回答,可以稍微缩短一下:

要改进Darin的回答,可以稍微缩短一下:

public class UniqueFileName : ValidationAttribute
{
    private readonly NewsService _newsService = new NewsService();

    public override bool IsValid(object value)
    {
        if (value == null) { return false; }

        var file = (HttpPostedFile) value;

        return _newsService.IsFileNameUnique(file.FileName);
    }
}

模型:

[UniqueFileName(ErrorMessage = "This file name is not unique.")]

请注意必须提供错误信息,否则错误将为空。


8

背景:

模型验证是必需的,以确保我们接收到的数据有效和正确,这样我们才能使用这些数据进行进一步处理。我们可以在操作方法中验证模型。内置的验证属性包括Compare、Range、RegularExpression、Required、StringLength。然而,在某些情况下,我们可能需要除内置属性之外的其他验证属性。

自定义验证属性

public class EmployeeModel 
{
    [Required]
    [UniqueEmailAddress]
    public string EmailAddress {get;set;}
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public int OrganizationId {get;set;}
}

要创建自定义验证属性,您需要从ValidationAttribute类派生此类。
public class UniqueEmailAddress : ValidationAttribute
{
    private IEmployeeRepository _employeeRepository;
    [Inject]
    public IEmployeeRepository EmployeeRepository
    {
        get { return _employeeRepository; }
        set
        {
            _employeeRepository = value;
        }
    }
    protected override ValidationResult IsValid(object value,
                        ValidationContext validationContext)
    {
        var model = (EmployeeModel)validationContext.ObjectInstance;
        if(model.Field1 == null){
            return new ValidationResult("Field1 is null");
        }
        if(model.Field2 == null){
            return new ValidationResult("Field2 is null");
        }
        if(model.Field3 == null){
            return new ValidationResult("Field3 is null");
        }
        return ValidationResult.Success;
    }
}

希望这能有所帮助。干杯!
参考资料:
- Code Project - ASP.NET MVC3中的自定义验证属性 - Haacked - ASP.NET MVC 2 自定义验证

1
有些晚了,但对于正在搜索的人来说仍然有用。您可以通过在数据注释中使用额外的属性来轻松实现此操作:
public string foo { get; set; }
public string bar { get; set; }

[MinLength(20, ErrorMessage = "too short")]
public string foobar 
{ 
    get
    {
        return foo + bar;
    }
}

其实就是这样。如果你真的想在特定位置显示验证错误,可以在视图中添加以下内容:

@Html.ValidationMessage("foobar", "your combined text is too short")

在视图中这样做可以方便地进行本地化。希望这可以帮到你!

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