MVC 3 預設採用 DataAnnotations 伺服端/客戶端驗證,但驗證規則分散在 Model 類別,View 中難以一眼看出欄位套用了哪些規則。本文示範**擴展方法 + 鏈式 API + jQuery Validation**,在 View 直接以流暢語法宣告驗證規則,並自動生成客戶端驗證腳本。
namespace MvcWeb.Extensions
{
// 1. JqueryHelper:擴展 WebViewPage,提供 .Jquery() 入口
public class JqueryHelper
{
public ViewContext ViewContext { get; private set; }
public WebViewPage ViewPage { get; private set; }
public RouteCollection RouteCollection { get; private set; }
public JqueryHelper(ViewContext vc, WebViewPage page, RouteCollection rc = null)
{
ViewContext = vc; ViewPage = page; RouteCollection = rc ?? RouteTable.Routes;
}
}
public static class JqueryExtensions
{
public static JqueryHelper Jquery(this WebViewPage page)
{
var key = typeof(JqueryHelper);
var helper = page.PageData[key] as JqueryHelper;
if (helper == null) page.PageData[key] = helper = new JqueryHelper(page.ViewContext, page);
return helper;
}
}
// 2. JqueryValidations:收集規則與訊息,生成 jQuery Validation 設定腳本
public class JqueryValidations
{
private readonly WebViewPage _page;
private readonly Dictionary> _rules = new();
private readonly Dictionary> _messages = new();
public JqueryValidations(WebViewPage page) { _page = page; }
// 鏈式規則方法
public void Required(string name, string msg) => AddRule(name, "required", true, msg);
public void Email(string name, string msg) => AddRule(name, "email", true, msg);
public void Number(string name, string msg) => AddRule(name, "number", true, msg);
public void Range(string name, int min, int max, string msg) => AddRule(name, "range", new[] { min, max }, msg);
private void AddRule(string name, string rule, object value, string msg)
{
if (!_rules.ContainsKey(name)) _rules[name] = new Dictionary();
_rules[name][rule] = value;
if (!string.IsNullOrEmpty(msg))
{
if (!_messages.ContainsKey(name)) _messages[name] = new Dictionary();
_messages[name][rule] = msg;
}
}
// 元素級鏈式進入點
public ValidationElement Element(string name) => new ValidationElement(name, this);
// 生成腳本
public string ToScripts(string formSelector)
{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var sb = new StringBuilder();
sb.Append("$("); serializer.Serialize(formSelector, sb); sb.Append(").validate(");
serializer.Serialize(new { rules = _rules, messages = _messages, onkeyup = false }, sb);
sb.Append(");");
return sb.ToString();
}
}
// 3. ValidationElement:提供 element.Required().Email()... 鏈式語法
public class ValidationElement
{
public string Name { get; }
public JqueryValidations Validations { get; }
public ValidationElement(string name, JqueryValidations v) { Name = name; Validations = v; }
public ValidationElement Required(string msg) { Validations.Required(Name, msg); return this; }
public ValidationElement Email(string msg) { Validations.Email(Name, msg); return this; }
public ValidationElement Number(string msg) { Validations.Number(Name, msg); return this; }
public ValidationElement Range(int min, int max, string msg) { Validations.Range(Name, min, max, msg); return this; }
}
// 4. JqueryHelper 擴展:.Validations() 取得/建立驗證收集器
public static class JQueryValidationExtensions
{
public static JqueryValidations Validations(this JqueryHelper helper, string name = "(default)")
{
var key = typeof(JqueryValidations) + "+" + name;
var val = helper.ViewPage.PageData[key] as JqueryValidations;
if (val == null) helper.ViewPage.PageData[key] = val = new JqueryValidations(helper.ViewPage);
return val;
}
}
}
@using MvcWeb.Extensions
<!DOCTYPE html>
<html>
<head>
<title>鏈式驗證 Demo</title>
<script src="@Url.Content("~/Scripts/jquery-1.5.1.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.js")"></script>
</head>
<body>
<form id="form" method="post">
<input type="text" id="user.Name" name="user.Name" />
@{ this.Jquery().Validations().Required("user.Name", "用戶名必填"); }
<select name="user.Gender">
<option value="">請選擇</option>
<option value="1">男</option>
<option value="2">女</option>
</select>
@{ this.Jquery().Validations().Required("user.Gender", null); }
<input type="text" name="user.Email" />
@{ this.Jquery().Validations().Element("user.Email").Required("Email必填").Email("格式錯誤"); }
<input type="submit" value="提交" />
</form>
<script type="text/javascript">
@Html.Raw(this.Jquery().Validations().ToScripts("#form"))
</script>
</body>
</html>
| 設計 | 效用 |
|---|---|
| `PageData` 快取 | 同一請求內多次呼叫 `.Validations()` 返回同一實例,規則自動合併 |
| 鏈式 `ValidationElement` | `Element("name").Required().Email().Range(...)` 可讀性極高 |
| `JavaScriptSerializer` 序列化 | 自動將規則字典轉為合法 JSON,避免手工拼接錯誤 |
| `@Html.Raw` 輸出 | 防止 Razor 自動 HTML 編碼導致腳本失效 |
| 擴展 `WebViewPage` 而非 `ViewPage` | MVC 3 Razor 視圖基類為 `WebViewPage`,擴展正確類別才能在 View 中使用 |
MVC 3 預設啟用 `jquery.validate.unobtrusive.js`,配合 Model `DataAnnotations` 自動生成 `data-val-*` 屬性,**零腳本即可完成客戶端驗證**。本文方案適用於:
利用 **擴展方法擴展 `WebViewPage`**、**`PageData` 跨方法共享狀態**、**鏈式 `ValidationElement` 收集規則**、**`JavaScriptSerializer` 生成 JSON**,即可在 Razor View 中寫出 `this.Jquery().Validations().Element("email").Required().Email()` 這樣流暢、可讀、可維護的驗證宣告。這展示了 C# 擴展方法與動態語言特性結合的強大表達力。