講完了Controller的基礎應用後,
本文將介紹ASP.Net Core中的Model Validation。
同步發表於個人點部落 - [鐵人賽Day15] ASP.Net Core MVC 進化之路 - Model Validation(1) / 前端 vs. 後端驗證
許多人將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; }
}
我們將Id及Title設定為必要輸入欄位,
之後在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。
一般常見的結構型別如int
、double
、float
、DateTime
等。
參考型別則如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/