【问题标题】:MVC 4 custom validation attribute firing twice on server sideMVC 4 自定义验证属性在服务器端触发两次
【发布时间】:2014-05-26 06:55:03
【问题描述】:

我正在使用从 RequiredAttribute 继承的自定义验证 requiredconditional 来有条件地要求某些字段。除了在一次情况下,这在任何地方都能很好地工作,我无法弄清楚发生了什么。它为模型中的一个属性调用 IsValid 方法两次(客户端验证完美运行)该模型有 2 个使用此属性的属性,但只有一个有问题。起初我认为这是因为我的一个条件是一个复选框,它同时回发了选中的值和隐藏的值,但我尝试使用单选按钮甚至隐藏值,但在所有情况下都有相同的结果。这是我的观点(为测试而简化):

@ModelType List(Of eLADNETBusiness.AdditionalInterest)

@Code
    ViewData("Title") = "Bind Coverage Entry"
End Code

<h2>Bind Coverage Entry</h2>
@Using Html.BeginForm()
    @Html.AntiForgeryToken() 
    @Html.Hidden("hullValue", ViewBag.HullValue)
    Dim currentCount As Integer = 0
    @For count As Integer = 0 To Model.Count - 1
            currentCount = count
             @<div class="editor-label">
                 @Html.LabelFor(Function(model) model(currentCount).LienholderAmount)
              </div>
             @<div class="editor-field">
                @Html.EditorFor(Function(model) model(currentCount).LienholderAmount)
                @Html.ValidationMessageFor(Function(model) model(currentCount).LienholderAmount)
             </div>
             @<div>
                @Html.EditorFor(Function(model) model(currentCount).Lienholder90Percent)
             </div>
     Next
    @<p>
        <input type="submit" value="Continue" />
    </p>

End Using

这是我的模型(为测试而简化):

<DataContract()> _
Public Class AdditionalInterest
<DataMember()> _
Public Property ID As Integer = 0 

<RequiredConditional("Lienholder90Percent", False, ErrorMessage:="Enter Breach of Warranty lienamount or select 90 percent of insured value")> _
<Display(Name:="Lienholder Amount")> _
<DataMember()> _
Public Property LienholderAmount As Nullable(Of Integer)    

<DataMember()> _
Public Property Lienholder90Percent As Boolean

结束类

还有我需要的条件属性:

Imports System.Collections.Generic
Imports System.Linq
Imports System.ComponentModel.DataAnnotations
Imports System.Web.Mvc
Imports System.Collections
Imports System.Text

Public Class RequiredConditional
Inherits RequiredAttribute
Implements IClientValidatable

Private Property PropertyNames() As String()
Private Property DesiredValues() As Object()

Public Sub New(comparePropertyNames As String(), comparePropertyDesiredValues As Object())
    PropertyNames = comparePropertyNames
    DesiredValues = comparePropertyDesiredValues
End Sub

Public Sub New(comparePropertyNames As String, comparePropertyDesiredValues As Object)
    PropertyNames = New String() {comparePropertyNames}
    DesiredValues = New String() {comparePropertyDesiredValues}
End Sub

Protected Overrides Function IsValid(value As Object, context As ValidationContext) As ValidationResult
    Dim instance As Object = context.ObjectInstance
    Dim type As Type = instance.GetType()
    Dim propertyvalue As Object
    Dim trueConditions As Integer = 0
    For count As Integer = 0 To PropertyNames.Count - 1
        propertyvalue = type.GetProperty(PropertyNames(count)).GetValue(instance, Nothing)
        If Not propertyvalue Is Nothing Then
            If DesiredValues.Count >= count + 1 Then
                If propertyvalue.ToString() = DesiredValues(count).ToString() Then
                    trueConditions += 1
                End If
            End If
        End If
    Next
    'if all conditions are met, validate value
    If trueConditions = PropertyNames.Count And trueConditions = DesiredValues.Count Then
        Dim result As ValidationResult = MyBase.IsValid(value, context)
        Return result
    End If

    Return ValidationResult.Success
End Function

Public Function GetClientValidationRules(metadata As System.Web.Mvc.ModelMetadata, context As System.Web.Mvc.ControllerContext) As System.Collections.Generic.IEnumerable(Of System.Web.Mvc.ModelClientValidationRule) _
    Implements System.Web.Mvc.IClientValidatable.GetClientValidationRules

    Dim results As New List(Of ModelClientValidationRule)
    Dim rule = New ModelClientValidationRule With {.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()), .ValidationType = "requiredif"}

    Dim depProp As String = String.Empty 

    Dim sbProp As New StringBuilder()
    Dim sbTarget As New StringBuilder()
    Dim helper As New ValidationHelper

    For count As Integer = 0 To PropertyNames.Count - 1
        Dim prop As String = PropertyNames(count)
        depProp = helper.BuildDependentPropertyName(metadata, TryCast(context, ViewContext), prop)
        sbProp.AppendFormat("|{0}", depProp)

        Dim targetValue As String = String.Empty
        If DesiredValues.Count >= count + 1 Then
            targetValue = (If(DesiredValues(count), "")).ToString()
        End If

        If DesiredValues(count).GetType() = GetType(Boolean) Then
            targetValue = DesiredValues(count).ToString.ToLower
        End If

        sbTarget.AppendFormat("|{0}", targetValue)

    Next

    rule.ValidationParameters.Add("dependentproperty", sbProp.ToString().TrimStart("|"))
    rule.ValidationParameters.Add("targetvalue", sbTarget.ToString().TrimStart("|"))


    results.Add(rule)
    Return results

End Function

End Class

因此,当我单击提交并调试到 requiredconditional 属性时,lienholderamount 属性命中 IsValid 两次。第一次命中时, Lienholder90Percent 的值是 False,即使在模型中它是真的并且在表单上是真的(并且通过了客户端验证),所以此时验证失败。然后再次点击,Lienholder90Percent 为 True(这是正确的)并通过验证。但由于第一个失败,它仍然失败并显示错误消息。您会注意到该模型是一个列表,但出于测试目的,我只发送一个并且仍然得到相同的结果。无法弄清楚为什么它只发生在这一属性上。希望这很容易,我只是看不到。我花了一整天的时间试图弄清楚这一点。就像我说的,我经常使用这个属性并且效果很好。找不到这种情况的不同之处。

【问题讨论】:

  • 所以我可以通过更改模型中属性的顺序来解决这个问题。将属性lienholder90percent 移到lienholderamount 属性上方(这取决于lienholder90percent 的值以进行验证)。会认为在调用 IsValid 函数之前设置了所有属性吗?我在之前使用此属性时很幸运,因为我所有的依赖属性都已经在属性被验证之前。
  • 这也解决了我的问题 - 您应该将此作为答案提交并接受。

标签: asp.net-mvc validation


【解决方案1】:

继承自 ValidationAttribute 而不是 RequiredAttribute

RequiredAttribute 的 IsValid 在属性填充后立即调用,而 ValidationAttribute 的 IsValid 在整个模型填充后调用(这对于更复杂的验证有意义)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-27
    • 1970-01-01
    • 2010-10-16
    • 2013-11-12
    • 1970-01-01
    相关资源
    最近更新 更多