Asp.net 当DataAnnotation失败时,IValidatableObject验证方法触发

Asp.net 当DataAnnotation失败时,IValidatableObject验证方法触发,asp.net,asp.net-mvc,asp.net-mvc-3,data-annotations,ivalidatableobject,Asp.net,Asp.net Mvc,Asp.net Mvc 3,Data Annotations,Ivalidatableobject,我有一个ViewModel,它有一些DataAnnotations验证,然后对于更复杂的验证,实现IValidatableObject并使用Validate方法 我所期望的行为是:首先是所有的DataAnnotation,然后是Validate方法(如果没有错误)。但我发现这并不总是真的。我的ViewModel(演示版)有三个文件,一个是字符串,一个是十进制,一个是十进制?。这三个属性都只有Required属性。对于字符串和十进制?来说,行为是预期的,但是对于十进制,当为空时,所需的验证失败(到

我有一个ViewModel,它有一些DataAnnotations验证,然后对于更复杂的验证,实现IValidatableObject并使用Validate方法

我所期望的行为是:首先是所有的DataAnnotation,然后是Validate方法(如果没有错误)。但我发现这并不总是真的。我的ViewModel(演示版)有三个文件,一个是
字符串
,一个是
十进制
,一个是
十进制?
。这三个属性都只有Required属性。对于
字符串
十进制?
来说,行为是预期的,但是对于
十进制
,当为空时,所需的验证失败(到目前为止还不错),然后执行验证方法。如果我检查属性,它的值为零

这是怎么回事?我错过了什么

注意:我知道Required属性是用来检查值是否为null的。因此,我希望被告知不要在不可为null的类型中使用Required属性(因为它永远不会触发),或者,该属性以某种方式理解POST值,并注意字段没有填充。在第一种情况下,属性不应触发,Validate方法应触发。在第二种情况下,属性应该触发,而Validate方法不应该触发。但我的结果是:属性触发器和Validate方法触发

以下是代码(没什么特别的):

控制器:

public ActionResult Index()
{
    return View(HomeModel.LoadHome());
}

[HttpPost]
public ActionResult Index(HomeViewModel viewModel)
{
    try
    {
        if (ModelState.IsValid)
        {
            HomeModel.ProcessHome(viewModel);
            return RedirectToAction("Index", "Result");
        }
    }
    catch (ApplicationException ex)
    {
        ModelState.AddModelError(string.Empty, ex.Message);
    }
    catch (Exception ex)
    {
        ModelState.AddModelError(string.Empty, "Internal error.");
    }
    return View(viewModel);
}
型号:

public static HomeViewModel LoadHome()
{
    HomeViewModel viewModel = new HomeViewModel();
    viewModel.String = string.Empty;
    return viewModel;
}

public static void ProcessHome(HomeViewModel viewModel)
{
    // Not relevant code
}
视图模型:

public class HomeViewModel : IValidatableObject
{
    [Required(ErrorMessage = "Required {0}")]
    [Display(Name = "string")]
    public string String { get; set; }

    [Required(ErrorMessage = "Required {0}")]
    [Display(Name = "decimal")]
    public decimal Decimal { get; set; }

    [Required(ErrorMessage = "Required {0}")]
    [Display(Name = "decimal?")]
    public decimal? DecimalNullable { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        yield return new ValidationResult("Error from Validate method");
    }
}
公共类HomeViewModel:IValidatableObject
{
[必需(ErrorMessage=“必需{0}”)]
[显示(Name=“string”)]
公共字符串{get;set;}
[必需(ErrorMessage=“必需{0}”)]
[显示(Name=“decimal”)]
公共十进制{get;set;}
[必需(ErrorMessage=“必需{0}”)]
[显示(Name=“decimal?”)]
公共十进制?小数为空{get;set;}
公共IEnumerable验证(ValidationContext ValidationContext)
{
返回新的ValidationResult(“来自Validate方法的错误”);
}
}
视图:

@model MVCTest1.ViewModels.HomeViewModel
@{
Layout=“~/Views/Shared/_Layout.cshtml”;
}
@使用(Html.BeginForm(null,null,FormMethod.Post))
{
@Html.ValidationSummary()
名义:
@Html.TextBoxFor(m=>m.Nombre)
十进制的:
@Html.TextBoxFor(m=>m.Decimal)
十进制的?:
@Html.TextBoxFor(m=>m.DecimalNullable)
接受器
超导体
@Html.HiddenFor(m=>m.Accion)
}

评论交流后的注意事项:

一致同意的结果是,
IValidatableObject
的方法
Validate()
仅在未触发任何验证属性时调用。简而言之,预期的算法如下(取自上一个链接):

  • 验证属性级别属性
  • 如果任何验证程序无效,则中止验证并返回失败
  • 验证对象级属性
  • 如果任何验证程序无效,则中止验证并返回失败
  • 如果在桌面框架上,并且对象实现了IValidatableObject,则调用其Validate方法并返回任何失败
  • 然而,使用问题代码,
    验证
    即使在
    [必需]
    触发后也会被调用。这似乎是一个明显的MVC错误。据报道

    三种可能的解决办法:

  • 除了打破MVC的预期行为之外,还有一个变通方法,尽管它的使用存在一些已知的问题。为避免同一字段显示多个错误,进行了一些更改,代码如下:

    viewModel
        .Validate(new ValidationContext(viewModel, null, null))
        .ToList()
        .ForEach(e => e.MemberNames.ToList().ForEach(m =>
        {
            if (ModelState[m].Errors.Count == 0)
                ModelState.AddModelError(m, e.ErrorMessage);
        }));
    
  • 忘记
    IValidatableObject
    ,只使用属性。它干净、直接,能够更好地处理本地化,并且在所有模型中,它的可重用性最好。只需为要执行的每个验证实现。您可以验证所有模型或特定属性,这取决于您。除了默认情况下可用的属性(DataType、Regex、Required等)之外,还有几个使用最多的验证库。实现“缺少的一个”的是

  • 仅实现
    IValidatableObject
    接口丢弃。如果这是一个非常特殊的模型,并且不需要太多验证,那么这似乎是一个合理的选择。在大多数情况下,如果使用了属性,开发人员将执行所有常规和常见的验证(即必需的验证等),这将导致在默认情况下已经实现的验证上的代码重复。也没有可重用性

  • 评论前回答:

    首先,我创建了一个新项目,从零开始,只使用您提供的代码。它从未同时触发数据注释和验证方法

    不管怎样,我知道

    根据设计,MVC3将
    [必需的]
    属性添加到不可为空的值类型,如
    int
    DateTime
    或是
    decimal
    。因此,即使您从该
    十进制
    中删除了必需的属性,它的工作方式也与该属性相同

    这是有争议的错误(或没有),但它的设计方式

    在你的例子中:

    • 如果[Required]存在且未给出值,则触发“DataAnnotation”。在我看来完全可以理解
    • 如果不存在[Required],但值不可为null,则触发“DataAnnotation”。有争议,但我倾向于同意它,因为如果属性不可为null,则必须输入一个值,否则不要向用户显示它,或者只使用可为null的
      十进制数
    在应用程序的启动方法中,可以通过以下方式关闭此行为:

    DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
    
    我想这房子的名字是不言自明的

    无论如何,我不明白为什么您希望用户输入一些不需要的内容,并且不使该属性为空。如果是nul
    DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
    
    public ActionResult Index(HomeViewModel viewModel)
    {
        // Complete values that the user may have 
        // not filled (all not-required / nullables)
    
        if (viewModel.Decimal == null) 
        {
            viewModel.Decimal = 0m;
        }
    
        // Now I can validate the model
    
        if (ModelState.IsValid)
        {
            HomeModel.ProcessHome(viewModel);
            return RedirectToAction("Ok");
        }
    }