iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 15
0
Modern Web

菜鳥練等區-ASP.Net Core MVC進化之路系列 第 15

[鐵人賽Day15] - Model Validation(1) / 前端 vs. 後端驗證

前言

講完了Controller的基礎應用後,

本文將介紹ASP.Net Core中的Model Validation。

同步發表於個人點部落 - [鐵人賽Day15] ASP.Net Core MVC 進化之路 - Model Validation(1) / 前端 vs. 後端驗證

Model Validation

許多人將Model Validation翻成模型驗證,
但筆者比較習慣稱呼它為資料驗證
資料驗證主要是檢查HTML表單中的欄位內容是否符合規則,
常見的如email格式、信用卡卡號、電話號碼等。

資料驗證又可分為前端驗證後端驗證
雖然都是作資料檢查的工作,
但在本質上還是有很大的差別。
前端驗證是在Client端使用Javascript預先檢查表單的內容,
後端驗證則在Server端作為資料保護最後一道防線,

簡單示意圖如下。

既然後端驗證是最後一道防線,
幹嘛還要脫褲子放屁作兩次?

使用前端驗證除了能有效減少Request的數量(因為後端驗證就會發Request),
也會讓使用者體驗比較舒服一點(畫面不會閃一下),
但後端驗證也能透過Ajax達到畫面不閃的效果(遠端驗證Remote Validation)。

理論上資料驗證是需要分開做兩次的,
但如果使用ASP.Net Core MVC開發網站,
你只需要做一次

我們要先來講講後端驗證的使用方式。
ASP.Net Core MVC中我們可以在Model中設定後端驗證,
只要在屬性掛上[ValidationAttribute]就可以達到後端驗證的效果。
這裡簡單摘列幾種內建常用的[ValidationAttribute]

  • [Required]:驗證欄位內容是否為空值或空字串。
  • [EmailAddress]:驗證欄位內容是否符合信箱格式。
  • [Compare]:比對兩個欄位內容是否相同。
  • [Range]:驗證欄位數值是否介於指定範圍中。
  • [RegularExpression]:驗證資料符合指定的規則運算式。
  • [MaxLength][MinLength][StringLength]:驗證字串長度。
  • [Url]:驗證欄位內容是否符合URL格式 。

我們來簡單的寫個範例。

public class Book
{
    [Required]
    public int Id { get; set; }

    [Required]
    public string Title{ get; set; }
        
    public DateTime PublishDate { get; set; }
}

我們將IdTitle設定為必要輸入欄位,
之後在Controller中加入下面程式碼。

[HttpGet]
public IActionResult Create()
{
    return View();
}

接著使用範本建立預設檢視(在程式碼區塊內按右鍵 > 新增檢視 )

按下新增後它按照我們提供的Model產生Create.cshtml
我們故意先將Id的部分註解調。

@model IronmenMvcWeb.Models.Book

@{
    ViewData["Title"] = "Create";
}

<h2>Create</h2>

<h4>Book</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <!--故意將Id註解掉-->
            @*<div class="form-group">
                <label asp-for="Id" class="control-label"></label>
                <input asp-for="Id" class="form-control" />
                <span asp-validation-for="Id" class="text-danger"></span>
            </div>*@
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="PublishDate" class="control-label"></label>
                <input asp-for="PublishDate" class="form-control" />
                <span asp-validation-for="PublishDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

接著建立[HttpPost]Action
請記得要加入ModelState.IsValid判斷,
才會對資料欄位執行後端驗證。

[HttpPost]
public IActionResult Create(Book book)
{
    if(ModelState.IsValid)
    {
        //Create book.
    }
    return View();
}

接著直接按下F5執行,
隨便填個內容之後按下Create送出。

透過Debug模式觀察Book資料,
可以看到我們雖然沒有填入Id的值,
但它預設幫我們產生「0」這個數值。

這導致了驗證的效果達不到預期的效果。

但為什麼Id並沒有被視為null呢?
在資料綁定(Model Binding)過程沒有收到資料時,
會將結構型別(struct type)使用初始值當作預設值
參考型別(reference type)的話則會顯示null
一般常見的結構型別intdoublefloatDateTime等。
參考型別則如string及一般的Class

在過去我們會使用Nullable<int>來解決這個問題(簡寫為int?)
然後接著觀察這個數值是否為null
ASP.Net Core有一個新玩意兒可以解決 - [BindRequired]
設定方法只要把它原封不動掛上去可以了。

public class Book
{
    [BindRequired]
    public int Id { get; set; }

    [Required]
    public string Title{ get; set; }
        
    public DateTime PublishDate { get; set; }
}

再觀察一次ModelState的狀態,
會發現一樣的操作模式卻顯示為false了。

如果想讓[Required]擁有[BindRequired]的特性可以參考這篇。

剛才講的全是後端驗證的方式,那前端驗證呢?
上面有提到前端是使用javascript來驗證的,
所以第一步我們要把前端驗證的套件加進來。
在範本專案中很貼心地已經幫我們安裝好了,
只要在.cshtml中加入下方程式碼即可啟動前端驗證。

@section scripts{
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
}

接著再測試一次,
不輸入資料就按下Create送出,
會得到一樣的驗證提示,
但差別在於畫面不會再轉了(沒有送Request)。

有關為什麼可以只做一次這個問題,
其實是Tag Helper的魔法。
View Engine.cshtml轉譯成.html的過程,
會讀取Model的驗證條件,
然後自動幫<input>的欄位加上前端驗證套件(javascript)看得懂的data-annotaion
Book中的Title屬性為例:

<input class="form-control input-validation-error" 
 type="text" data-val="true" data-val-required="The Title field is required." 
 id="Title" name="Title" value="" 
 aria-describedby="Title-error" aria-invalid="true">

使用時請記得把前端套件include進來,
不然前端驗證是不會動的!

關於前後端的驗證簡單介紹到這邊,
下篇會介紹如何撰寫自訂的驗證。

參考

https://docs.microsoft.com/zh-tw/aspnet/core/mvc/models/validation?view=aspnetcore-2.1
https://www.strathweb.com/2017/12/required-and-bindrequired-in-asp-net-core-mvc/


上一篇
[鐵人賽Day14] - Controller
下一篇
[鐵人賽Day16] - Model Validation(2) / 自訂及遠端驗證
系列文
菜鳥練等區-ASP.Net Core MVC進化之路30

尚未有邦友留言

立即登入留言