2011-11-02 144 views
4

在MVC 3中使用的当前验证方法似乎是ValidationAttributes。我有一个非常特定于该模型的类验证,并且有几个属性之间的交互。MVC 3模型的复杂验证

基本上模型有一个其他模型的集合,他们都编辑在同一个表格。我们称之为ModelA,它有一个ModelB集合。我可能需要验证的一件事是ModelB的某些属性的总和小于ModelA的一个属性。用户可以在一些选项中划分X个点数。

ValidationAttributes是非常通用的,我不确定他们是否适合这项工作。

我不知道IDateErrorInfo在MVC 3中是如何被支持的,以及它是否可以直接使用。

一种方法是通过方法进行验证,但这意味着我无法进行客户端验证。

什么是正确的方式来做这样的事情?我还有更多选择吗?我低估了ValidationAttribute的力量吗?

回答

5

IDateErrorInfo

IDateErrorInfo由MVC框架支持(微软的教程可以发现here)。默认模型联编程序将负责通过将html表单元素绑定到模型来重新创建模型对象。如果模型联编程序检测到模型实现了接口,那么它将使用接口方法来验证模型中的每个属性,或者将模型作为一个整体进行验证。有关更多信息,请参阅教程。

如果你想使用此方法,然后使用客户端验证(引用史蒂夫·桑德森)“最直接的方式,以充分利用额外的验证规则是手动生成视图所需要的属性”:

<p> 
@Html.TextBoxFor(m.ClientName, new { data_val = "true", data_val_email = "Enter a valid email address", data_val_required = "Please enter your name"}) 

@Html.ValidationMessageFor(m => m.ClientName) 
</p> 

这可以用来触发任何已定义的客户端验证。请参阅下面的示例以了解如何定义客户端验证。

明确验证

正如你所说,你可以明确地验证在动作模型。例如:

public ViewResult Register(MyModel theModel) 
{ 
    if (theModel.PropertyB < theModel.PropertyA) 
     ModelState.AddModelError("", "PropertyA must not be less then PropertyB"); 

    if (ModelState.IsValid) 
    { 
     //save values 
     //go to next page 
    } 
    else 
    { 
     return View(); 
    } 
} 

在视图你会然后需要使用@Html.ValidationSummary显示错误消息作为上述代码将增加一个模型电平误差,而不是一个属性电平误差。

要指定你可以写一个属性级别的错误:

ModelState.AddModelError("PropertyA", "PropertyA must not be less then PropertyB"); 

然后在视图中使用:

@Html.ValidationMessageFor(m => m.PropertyA); 

显示错误消息。

此外,任何客户端验证都需要通过在视图中通过定义属性手动链接到客户端验证来链接。

定义模型验证属性

如果我理解正确的问题,您要验证它包含一个单值和集合,其中在集合的属性是要总结的模型。

对于我将给出的例子,视图将向用户呈现最大值字段和5个值字段。最大值字段将是模型中的单个值,其中5个值字段将成为集合的一部分。验证将确保值字段的总和不大于最大值字段。验证将被定义为模型上的一个属性,该属性也将很好地链接到JavaScript客户端的阈值。

的观点:

@model MvcApplication1.Models.ValueModel 

<h2>Person Ages</h2> 

@using (@Html.BeginForm()) 
{ 
    <p>Please enter the maximum total that will be allowed for all values</p> 
    @Html.EditorFor(m => m.MaximumTotalValueAllowed) 
    @Html.ValidationMessageFor(m => m.MaximumTotalValueAllowed) 

    int numberOfValues = 5; 

    <p>Please enter @numberOfValues different values.</p> 

    for (int i=0; i<numberOfValues; i++) 
    { 
     <p>@Html.EditorFor(m => m.Values[i])</p> 
    } 

    <input type="submit" value="submit"/> 
} 

,因为我不想过于复杂的例子我还没有添加任何验证对值字段。

模型:

public class ValueModel 
{ 
    [Required(ErrorMessage="Please enter the maximum total value")] 
    [Numeric] //using DataAnnotationExtensions 
    [ValuesMustNotExceedTotal] 
    public string MaximumTotalValueAllowed { get; set; } 

    public List<string> Values { get; set; } 
} 

以下动作:

public ActionResult Index() 
{ 
    return View(); 
} 

[HttpPost] 
public ActionResult Index(ValueModel model) 
{ 
    if (!ModelState.IsValid) 
    { 
     return View(model); 
    } 
    else 
    { 
     return RedirectToAction("complete"); //or whatever action you wish to define. 
    } 
} 

自定义属性:

在模型定义的[ValuesMustNotExceedTotal]属性可以通过重写来定义Valida tionAttribute类:

public class ValuesMustNotExceedTotalAttribute : ValidationAttribute 
{ 
    private int maxTotalValueAllowed; 
    private int valueTotal; 

    public ValuesMustNotExceedTotalAttribute() 
    { 
     ErrorMessage = "The total of all values ({0}) is greater than the maximum value of {1}"; 
    } 

    public override string FormatErrorMessage(string name) 
    { 
     return string.Format(ErrorMessageString, valueTotal, maxTotalValueAllowed); 
    } 

    protected override ValidationResult IsValid(object value, ValidationContext validationContext) 
    { 
     PropertyInfo maxTotalValueAllowedInfo = validationContext.ObjectType.GetProperty("MaximumTotalValueAllowed"); 
     PropertyInfo valuesInfo = validationContext.ObjectType.GetProperty("Values"); 

     if (maxTotalValueAllowedInfo == null || valuesInfo == null) 
     { 
      return new ValidationResult("MaximumTotalValueAllowed or Values is undefined in the model."); 
     } 

     var maxTotalValueAllowedPropertyValue = maxTotalValueAllowedInfo.GetValue(validationContext.ObjectInstance, null); 
     var valuesPropertyValue = valuesInfo.GetValue(validationContext.ObjectInstance, null); 

     if (maxTotalValueAllowedPropertyValue != null && valuesPropertyValue != null) 
     { 
      bool maxTotalValueParsed = Int32.TryParse(maxTotalValueAllowedPropertyValue.ToString(), out maxTotalValueAllowed); 

      int dummyValue; 
      valueTotal = ((List<string>)valuesPropertyValue).Sum(x => Int32.TryParse(x, out dummyValue) ? Int32.Parse(x) : 0); 

      if (maxTotalValueParsed && valueTotal > maxTotalValueAllowed) 
      { 
       return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName)); 
      } 
     } 

     //if the maximum value is not supplied or could not be parsed then we still return that the validation was successful. 
     //why? because this attribute is only responsible for validating that the total of the values is less than the maximum. 
     //we use a [Required] attribute on the model to ensure that the field is required and a [Numeric] attribute 
     //on the model to ensure that the fields are input as numeric (supplying appropriate error messages for each). 
     return null; 
    } 
} 

添加客户端验证到自定义属性:

要客户端验证到这个属性就需要实现IClientValidatable接口:

public class ValuesMustNotExceedTotalAttribute : ValidationAttribute, IClientValidatable 
{ 
//...code as above... 

    //this will be called when creating the form html to set the correct property values for the form elements 
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) 
    { 
     var rule = new ModelClientValidationRule { 
      ValidationType = "valuesmustnotexceedtotal", //the name of the client side javascript validation (must be lowercase) 
      ErrorMessage = "The total of all values is greater than the maximum value." //I have provided an alternative error message as i'm not sure how you would alter the {0} and {1} in javascript. 
     }; 

     yield return rule; 
     //note: if you set the validation type above to "required" or "email" then it would use the default javascript routines (by those names) to validate client side rather than the one we define 
    } 
} 

如果您将在此时运行该应用程序并查看定义属性的字段的源html,您将看到以下内容:

<input class="text-box single-line" data-val="true" data-val-number="The MaximumTotalValueAllowed field is not a valid number." data-val-required="Please enter the maximum total value" data-val-valuesmustnotexceedtotal="The total of all values is greater than the maximum value." id="MaximumTotalValueAllowed" name="MaximumTotalValueAllowed" type="text" value="" /> 

特别要注意验证属性data-val-valuesmustnotexceedtotal。这就是我们的客户端验证如何链接到验证属性。

添加客户端验证:

要添加您需要在视图的标签添加类似以下库引用客户端验证:

<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script> 
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> 
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> 

您还需要确保客户端验证在web.config中打开,但我认为这应该是默认情况下:

<add key="ClientValidationEnabled" value="true"/> 
<add key="UnobtrusiveJavaScriptEnabled" value="true"/> 

剩下的就是在视图中定义客户端验证。注意,这里添加的验证在视图中定义,但如果它是在一个库,然后自定义属性(也许不是这个)定义可以添加其他车型其他观点:

<script type="text/javascript"> 

    jQuery.validator.unobtrusive.adapters.add('valuesmustnotexceedtotal', [], function (options) { 
     options.rules['valuesmustnotexceedtotal'] = ''; 
     options.messages['valuesmustnotexceedtotal'] = options.message; 
    }); 

    //note: this will only be fired when the user leaves the maximum value field or when the user clicks the submit button. 
    //i'm not sure how you would trigger the validation to fire if the user leaves the value fields although i'm sure its possible. 
    jQuery.validator.addMethod('valuesmustnotexceedtotal', function (value, element, params) { 

     sumValues = 0; 

     //determine if any of the value fields are present and calculate the sum of the fields 
     for (i = 0; i <= 4; i++) { 

      fieldValue = parseInt($('#Values_' + i + '_').val()); 

      if (!isNaN(fieldValue)) { 
       sumValues = sumValues + fieldValue; 
       valueFound = true; 
      } 
     } 

     maximumValue = parseInt(value); 

     //(if value has been supplied and is numeric) and (any of the fields are present and are numeric) 
     if (!isNaN(maximumValue) && valueFound) { 

      //perform validation 

      if (sumValues > maximumValue) 
      { 
       return false; 
      } 
     } 

     return true; 
    }, ''); 

</script> 

这应该是它。我确信可以在这里和那里做出一些改进,如果我误解了这个问题,你应该能够根据需要调整验证。但我相信这种验证似乎是大多数开发人员编写自定义属性的方式,包括更复杂的客户端验证。

希望这会有所帮助。如果您对上述问题有任何疑问或建议,请告知我们。

+0

Explicit可能不支持ClientSide验证。但是,自定义的一个是相当不错的,但我仍然认为这个解决方案对于一个特定的问题有点过于通用。 –

+0

@Ingo Vals - 我想我以前的回答误解了你的问题。我已经更新了答案,希望这是一个更有用的解决方案。希望能帮助到你。 – Dangerous

1

你的模型类可以实现IValidatableObject接口。

通过这种方式,您可以访问模型类的所有属性,并且可以执行所有自定义验证。

您也有IClientValidatable接口,用于客户端验证,但我不确定是否通过直接在模型类中实现客户端验证是由MVC挑选的,因为我只使用此接口来指定客户端验证自定义验证属性。

+0

这与ValidationAttribute相比如何工作? ModelState.IsValid的工作原理是否相同? –

+0

“IValidatableObject”被MVC自动拾取,结果将反映在“ModelState.IsValid”中。关于客户端验证的界面,我不确定,因为从未在模型类中直接使用过。 –

+0

我现在正在检查Knockout.js以及客户端的东西,所以我必须看到什么与此一起工作。 –

1

我得到了一些类似的情况也一样,我需要比较物业A和物业B中的价值,我得到它的实现:

public sealed class PropertyAAttribute : ValidationAttribute 
{ 
    public string propertyBProperty { get; set; } 
    // Override the isValid function 
    public override bool IsValid(object value) 
    { 
     // Do your comparison here, eg: 
      return A >= B; 
    } 
} 

然后,只需使用自定义验证属性是这样的:

[PropertyA(propertyBProperty = "PropertyB")] 
public string Property A {get; set;} 

我也非常努力地试着从别人那里得到这个解决方案,希望这个帮助!

+0

虽然这工作,我不喜欢弱类型,传递属性作为字符串的解决方案。我开始认为现在没有现存的真正解决方案。 –