ASP.NET MVC - 可以移除Model的驗證嗎!?

11 October 2011 — Written by Sky Chang
#ASP.NET MVC

開始前,我就直接講答案了,原則上,是不行於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 Nullable Sn { 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欄位來輸入,所以這兩種類別看起來一樣,但本質上卻是不同的了,所以分開反而是一件好事,另外,如果真的覺得維護很麻煩,也有AutoMapperValueInjecter這兩種方法來進行映射,但繼續介紹下去,就越離越遠了XDD…

結論

如一開始所講的,沒辦法動態時期移除Model的驗證,而根據國外大師們的建議,也認為ViewModel是比較好的一種做法,但有的時候,或許上面的其他解法才是適合你的,畢竟解法並非只有一種,根據不同的狀況,用不同的解法,才是最好的。

Sky & Study4.TW