ASP.NET MVC - 可以移除Model的驗證嗎!?
開始前,我就直接講答案了,原則上,是不行於Runtime移除Model設定的驗證,但是有列出幾種做法可以給大家參考看看。
我們都知道ASP.NET MVC的Model驗證是非常強大又好用的,我們只要在Model裡面定義好,後續無論是Client端,或是Server端,都可以自動產生出驗證功能,來讓我們方便的使用。
有些地方需要特定的Model驗證,有些地方不需要!?
這是我使用Model驗證碰到的一個問題,舉例來說,我新增Customer和更新Customer的地方,客戶編號、客戶姓名是必填,所以我的Customer的地方會有這樣的程式碼。
[DisplayName("客戶編號")] [Required(ErrorMessage = "請輸入客戶編號")] [Range(1, 10000,ErrorMessage="範圍值為1~99999")] public virtual object Sn { get; set; } [DisplayName("中文名稱")] [Required(ErrorMessage = "請輸入中文名稱")] [StringLength(32, ErrorMessage = "請勿輸入超過32個字")] public virtual object CName { get; set; }
這樣是很合理的Model驗證寫法,但是,今天碰到一個問題,如果我的Search裡面也有這些欄位要填寫,但是Sn和CName卻不是必填,這時候該怎麼辦!?
幾種解法 – Server端解法
第一種解法,就是把Required這個屬性欄位拿掉,這樣就不會驗證到未填寫的錯誤,但是換言之,新增與修改的地方也變成可以不用填寫這兩個欄位;這樣當然不行啊!!,所以我們可以在Controller裡面加上ModelState.AddModelError()這個方法。
[HttpPost] public ActionResult Add(Customer customer) { //利用ModelState.AddModelError來產生錯誤 if (customer.Sn == null) ModelState.AddModelError("Sn", "請輸入客戶編號"); if (string.IsNullOrEmpty(customer.CName)) ModelState.AddModelError("CName", "請輸入中文名稱"); if (!ModelState.IsValid) { return View(cust); } else { //寫入資料庫程式碼... } }
ModelState.AddModelError()會產生一個錯誤訊息,所以我們可以利用這種方式來替代原本拿掉的Required,但是這種解法也有缺點,如上面所示,新增的Controller也要改,更新的Controller也要改,而且這種方法,也只有Server端的驗證,Client是沒有作用的。
幾種解法 – Client端解法
剛剛是Server端的解法,我們可以搭配Client端的驗證,其實在ASP.NET MVC 3的時候就開始配合HTML5,並導入了data-val的屬性,利用這個屬性,就可以輕鬆地去自訂驗證,( 當然還是要配合jQuery啦 ),我們可以看到其實透過ASP.NET MVC的Html.Helper配合Model,就會自動產生以下的程式碼,而每個data-val-*,其實就是對應到Model所設定的驗證,所以我們可以在新增與更新的頁面,不使用Html.EditorFor來產生HTML,而是用手刻的方式去自己寫HTML,如下:
<dt><label for="">客戶編號</label> :</dt> <dd><input class="text-box single-line" data-val="true" data-val-number="欄位 客戶編號 必須是數字。" data-val-range="範圍值為1~99999" data-val-range-max="10000" data-val-range-min="1" data-val-required="請輸入客戶編號" id="Sn" name="Sn" type="text" /></dd> <dd><span class="field-validation-valid" data-valmsg-for="CusNo" data-valmsg-replace="true"></span></dd> <dt><label for="CName">中文姓名</label>:</dt> <dd><input class="text-box single-line" data-val="true" data-val-length="請勿輸入超過32個字" data-val-length-max="32" data-val-required="請輸入中文名稱" id="CName" name="CName" type="text" value="" /></dd> <dd><span class="field-validation-valid" data-valmsg-for="CName" data-valmsg-replace="true"></span></dd>
這個方法的重點就在於手工加上data-val-required屬性來驗證,搭配Server端,就可以達到Client和Server的驗證,但缺點還是一樣,很多頁面,就要改很多次,也變得比較麻煩。
幾種解法 – ViewModel
其實這個沒什麼技巧,只是針對Search這個頁面特別的去寫一個Model,( 針對某頁面而產生的Model,稱為ViewMode ),然後所有的驗證就可以於ViewModel裡面去撰寫處理,這樣就可以和新增還有修改的地方去做隔離,缺點是要做多建立一個ViewModel,如下,我們建立一個SearchCustomerViewModel。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel.DataAnnotations; using System.ComponentModel; namespace SkyTestViewModel { public class SearchCustomerViewModel { [DisplayName("客戶編號")] [Range(1, 10000, ErrorMessage = "範圍值為1~99999")] public NullableSn { get; set; } [DisplayName("中文名稱")] [StringLength(32, ErrorMessage = "請勿輸入超過32個字")] public string CName { get; set; } } }
其實和一般我們常用的Model沒什麼差異,接下來我們看看View的部分。
@model SkyTestViewModel.SearchCustomerViewModel <fieldset> <legend>快速搜尋</legend> <dl> <dt>@Html.LabelFor(model => model.Sn):</dt> <dd>@Html.EditorFor(model => model.Sn)</dd> <dd>@Html.ValidationMessageFor(model => model.Sn)</dd> <dt>@Html.LabelFor(model => model.CName):</dt> <dd>@Html.EditorFor(model => model.CName)</dd> <dd>@Html.ValidationMessageFor(model => model.CName)</dd> </dl> </fieldset> <input type="submit" value="查詢" />
這裡也和一般沒兩樣,只不過我們把model的部分換成SearchCustomerViewModel而已,然後是Controller的部分。
[HttpGet] public ActionResult SearchResult(SearchCustomerViewModel SearchCustomerViewModel) { //取得資料...並且秀出資料 }
後續就使用SearchCustomerViewModel進行處理,當然,這種最大的缺點就是會產生兩個幾乎類似的類別,維護也會變得比較麻煩,但這種還是最推薦的做法,而且反過來看,真的是維護比較麻煩嗎?,假設說Customer裡面有三個Tel欄位,但是SearchCustomerViewMode原則上只會有一個Tel欄位來輸入,所以這兩種類別看起來一樣,但本質上卻是不同的了,所以分開反而是一件好事,另外,如果真的覺得維護很麻煩,也有AutoMapper和ValueInjecter這兩種方法來進行映射,但繼續介紹下去,就越離越遠了XDD…
結論
如一開始所講的,沒辦法動態時期移除Model的驗證,而根據國外大師們的建議,也認為ViewModel是比較好的一種做法,但有的時候,或許上面的其他解法才是適合你的,畢竟解法並非只有一種,根據不同的狀況,用不同的解法,才是最好的。