2011-11-02 148 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

雖然這工作,我不喜歡弱類型,傳遞屬性作爲字符串的解決方案。我開始認爲現在沒有現存的真正解決方案。 –