在簡單了解了Unobtrusive JavaScript形式的驗證在jQuery中的編程方式之后,我們來介紹ASP.NET MVC是如何利用它實現客戶端驗證的。服務端驗證最終實現在相應的ModelValidator中,而最終的驗證規則定義在相應的ValidationAttribute中;而客戶端驗證規則通過HtmlHelper<TModel>相應的擴展方法(比如TextBoxFor、EditorFor和EdidtorForModel等)出現在生成的被驗證HTML元素中。毫無疑問,服務端驗證和客戶端驗證必須采用相同的驗證規則,那么通過應用ValidationAttribute特性定義的驗證規則也同樣體現在基於客戶端驗證規則的HTML上。[本文已經同步到《How ASP.NET MVC Works?》中]
一、ValidationAttribute與HTML
ASP.NET MVC默然采用基於ValidationAttribute特性的聲明式Model驗證,服務端驗證最終實現在兩個重寫的IsValid方法中。對於客戶端驗證,ASP.NET MVC對jQuery的驗證插件進行了擴展,實現了另一種不同的內聯方式是我們 可以將驗證規則定義在被驗證輸入元素的屬性中。為了讓客戶端和服務端采用相同的驗證規則,應用在Model類型某個屬性上的ValidationAttribute特性最終會體現在目標屬性對應的HTML元素上。
1: public class Contact
2: {
3: [DisplayName("姓名")]
4: [Required(ErrorMessage ="請輸入{0}!")]
5: [StringLength(8, ErrorMessage="作為{0}字符串長度不能超過{1}!")]
6: public string Name { get; set; }
7:
8: [DisplayName("電子郵箱地址")]
9: [RegularExpression(@"^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$",ErrorMessage="請輸入正確的電子郵箱地址!")]
10: public string EmailAddress { get; set; }
11: }
假設我們具有如上一個數據類型Contact,RequiredAttribute和StringLengthAttribute特性應用到表示姓名的Name屬性上用於確保用於必須輸入一個不超過128個字符的字符串,而表示Email地址的EmailAddress屬性應用了一個RegularExpressionAttribute用於確保用於輸入一個合法的Email地址。在一個以此Contact為Model類型的View中,如果我們調用HtmlHelper<TModel>的擴展方法EditorForModel,最終會生成如下一段HTML。
1: <div class="editor-label">
2: <label for="Name">姓名</label>
3: </div>
4:
5: <div class="editor-field">
6: <input class="text-box single-line"
7: data-val ="true"
8: data-val-length ="作為姓名字符串長度不能超過8!"
9: data-val-length-max ="8"
10: data-val-required ="請輸入姓名!"
11: id="Name" name="Name" type="text" value="" />
12: <span class="field-validation-valid" data-valmsg-for="Name" data-valmsg-replace="true"></span>
13: </div>
14:
15: <div class="editor-label">
16: <label for="EmailAddress">電子郵箱地址</label>
17: </div>
18:
19: <div class="editor-field">
20: <input class="text-box single-line"
21: data-val ="true"
22: data-val-regex ="請輸入正確的電子郵箱地址!"
23: data-val-regex-pattern ="^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$"
24: id="EmailAddress" name ="EmailAddress" type="text" value="" />
25: <span class="field-validation-valid" data-valmsg-for="EmailAddress" data-valmsg-replace="true"></span>
26: </div>
通過上面的這段HTML我們可以看到,對應着Model對象兩個屬性的<input>元素具有一個“data-val”屬性和一系列以“data-val-”為前綴的屬性,前者表示是否需要對用戶輸入的值進行驗證,后者則代表相應的驗證規則。具體來說,去除“data-val-”前綴后的屬性名稱對應着采用jQuery驗證時對應的驗證規則名稱。
一般來說,一個ValidationAttribute對應着一種驗證類型和一系列可選的驗證參數。比如RequiredAttribute、StringLengthAttribute和RegularExpressionAttribute對應的驗證類型分別是“required”、“length”和“regex”,而StringLengthAttribute和RegularExpressionAttribute各自具有一個驗證參數length-max(表示允許的字符串最大長度)和regex-pattern(正則表達式)。驗證錯誤消息一般作為驗證類型屬性的值,而驗證參數對應的屬性值自然就是相應的屬性值。
對於上面生成的HTML還有一點值得一提的是:對應着被驗證屬性的<input>元素會緊跟一個<span>元素用於顯示驗證失敗后的錯誤消息。該<span>元素的CSS類型為“field-validation-valid”,我們可以通過它來定制錯誤消息的顯示樣式。
二、客戶端驗證規則的生成
ASP.NET MVC在利用jQuery進行客戶端驗證的時候,雖然驗證規則並沒有采用其原生的方式通過被驗證元素的class屬性來提供,但是卻可以通過“data-val-{rulename}”的命名模式提取相應的驗證規則屬性值,並最終得到一樣驗證規則,ASP.NET MVC只需要對兩種作簡單的適配即可。
我們現在關心的是當我們調用HtmlHelper<TModel>相應的擴展方法將Model對象的某個屬性以表單輸入元素的形式呈現的時候是如何生成這些以“data-val-”為前綴的驗證屬性的呢?在這里我們需要涉及到一個重要的類型ModelClientValidationRule,顧名思義,ModelClientValidationRule用於描述客戶端驗證規則。如下面的代碼所示,ModelClientValidationRule具有三個屬性,字符串屬性ErrorMessage和ValidationType表示驗證錯誤消息和驗證的類型,類型為IDictionary<string, object>的只讀屬性ValidationParameters表示輔助客戶端驗證的參數,其中Key和Value分別表示驗證參數名和參數值。
1: public class ModelClientValidationRule
2: {
3: public string ErrorMessage { get; set; }
4: public string ValidationType { get; set; }
5: public IDictionary<string, object> ValidationParameters { get; }
6: }
7:
8: public abstract class ModelValidator
9: {
10: //其他成員
11: public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules();
12: public abstract IEnumerable<ModelValidationResult> Validate(object container);
13: }
通過前面的介紹我們知道抽象類ModelValidator中具有一個虛方法GetClientValidationRules用於返回一個ModelClientValidationRule對象的列表。對於所有支持客戶端驗證的ModelValidator來說,它必須重寫該方法以通過重寫Validate方法實現的服務端驗證邏輯相一致的客戶端驗證規則。
以用於進行范圍驗證的RangeAttribute特性對應的RangeAttributeAdapter為例,通過如下的代碼片斷我們知道它重寫了GetClientValidationRules並返回一個ModelClientValidationRangeRule對象元素的列表,該ModelClientValidationRule對象的驗證類型為“range”,采用RangeAttributeAdapter的ErrorMessage屬性作為自身的錯誤消息,作為驗證范圍的上、下限的值成為了該ModelClientValidationRule的兩個驗證參數,參數分別為“min”和“max”。
1: public class RangeAttributeAdapter : DataAnnotationsModelValidator<RangeAttribute>
2: {
3: //其他成員
4: public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
5: {
6: string errorMessage = base.ErrorMessage;
7: return new ModelClientValidationRangeRule[] { new ModelClientValidationRangeRule(errorMessage, base.Attribute.Minimum, base.Attribute.Maximum) };
8: }
9: }
10:
11: public class ModelClientValidationRangeRule : ModelClientValidationRule
12: {
13: public ModelClientValidationRangeRule(string errorMessage, object minValue, object maxValue)
14: {
15: base.ErrorMessage = errorMessage;
16: base.ValidationType = "range";
17: base.ValidationParameters["min"] = minValue;
18: base.ValidationParameters["max"] = maxValue;
19: }
20: }
客戶端驗證還在這里涉及到一個重要的接口IClientValidatable,它具有唯一的GetClientValidationRules方法返回一個以ModelClientValidationRule對象表示的客戶端驗證規則列表。
1: public interface IClientValidatable
2: {
3: IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context);
4: }
對於所有支持客戶端驗證的ValidationAttrubute來說,都需要實現IClientValidatable接口並通過實現GetClientValidationRules方法提供對應的驗證規則,而生成的驗證規則需要與通過重寫的IsValid方法實現的服務端驗證邏輯一致。DataAnnotationsModelValidator重寫了GetClientValidationRules方法,如果對應的ValidationAttribute實現了IClientValidatable接口,它(ValidationAttribute)的GetClientValidationRules方法被調用返回的ModelClientValidationRule列表作為該方法的返回值。
當我們在某個View中調用HtmlHelper<TModel>的擴展方法將Model對象的某個屬性以表單輸入元素呈現出來的時候,會采用我們前面介紹的ModelValidator的提供機制根據目標屬性對應的Model元數據創建相應的ModelValidator,然后調用GetClientValidationRules方法得到一組表示客戶端驗證規則的ModelClientValidationRule列表。如果該列表不為空,它們將作為驗證屬性附加到目標屬性對應的<input>元素中。
ASP.NET MVC的客戶端驗證:jQuery的驗證
ASP.NET MVC的客戶端驗證:jQuery驗證在Model驗證中的實現
ASP.NET MVC的客戶端驗證:自定義驗證