統一欄位格式定義及驗證設計範例 – 後端驗證

[ASP.NET Core MVC][Vue3] 統一欄位格式定義及驗證設計範例 – 後端驗證 #CH1

在開發網站系統設計階段,對於欄位格式定義 (Data Annotation) 及驗證 (Validate) 就要先設計好,讓相同欄位在不同頁面使用時,都能維持相同的格式驗證,當欄位格式有異動時,也只需要修改一個地方就好,讓所有使用到的頁面都能一致同步修改。

例如專案有一個欄位叫 ”學號”,資料庫欄位名稱為 “StudentID”,格式為數字,長度固定為 10 碼,當在設計頁面時有用到這個欄位輸入,我們會在 Model 欄位附加屬性。

Data Annotation

如果每次都要指定格式及長度的話,就會過於重複設定了,如果未來格式有異動,那回頭檢查程式就要檢查很多地方,如果漏掉修改,就會產生 Bug 了。
而且當專案為多數人共同開發時,難免會發生有人設定錯誤或是沒設定,導致錯誤發生。

當相同欄位名稱應該有相同格式及驗證時,我建議可以統一放在資料表集中管理,當頁面使用到這欄位時,由程式自動驗證格式,減少人為疏失。

此範例環境使用 ASP.NET Core MVC 版本是 .NET6,前端使用 Vue3 框架,後端使用 Dapper 套件連接 SQL Server 2019,文末有範例可以下載。

通常在做資料的格式檢查時,會分別在前端與後端檢查,這兩者都很重要,在後端檢查是做最後的把關,避免有問題的資料寫入,而在前端檢查可以快速回應給使用者,避免過多不需要的後端傳送要求。

此範例先實作後端驗證的部份,待下一篇文章再來實作前端的欄位即時檢查。

建立專案

開啟 Visual Studio,建立新專案為「ASP.NET Core Web 應用程式 (Model-View-Controller)」。

ASP.NET Core Web 應用程式 (Model-View-Controller)

輸入專案名稱、路徑。

架構選擇「.NET 6.0」版本,按下「建立」就會建立此專案。

架構選擇「.NET 6.0」版本,按下「建立」就會建立此專案

引用 Vue3 套件

打開「\Views\Shared\_Layout.cshtml」,在下方引用 JavaScript 部份加上引用 Vue3。

<script src="https://unpkg.com/vue@3"></script>

引用 Vue3 套件

停用 Json 回傳預設小寫設定

在 .NET Framework 使用 Json 回傳時,前端收到的 Json 物件大小寫設定與 ViewModel 相同,而在 .NET Core 時則預設開頭為小寫 (駝峰式命名),這裡我都會調整成與 ViewModel 大小寫相同。

在 Program.cs 加入以下語法:

停用 Json 回傳預設小寫設定

欄位定義資料表語法

我會建立一個資料表,來定義欄位的名稱、格式、長度及範圍,當頁面用到這欄位名稱時,就會採用資料表內的設定,以方便統一管理與修改。

建立資料表語法:

欄位 ColumnID 是專案會用到的欄位名稱,通常會和資料表內設定一樣的名稱。
欄位 ColumnName 是顯示的中文名稱,當有錯誤時就會顯示這個名稱給使用者看。
欄位 ColumnFormat 是設定要檢查的格式,可以自定名稱,在程式內會依照這個名稱檢查格式邏輯,通常可用正規表達式來檢查。
欄位 ColumnMaxLength, ColumnMinLength 是限制文字長度。
欄位 ColumnRangeStart, ColumnRangeEnd 是當格式為數字時,可以限制數字範圍的大小。

這裡我繼續新增 7 筆測試資料,分別定義不同的欄位格式及限制。

欄位定義資料表語法

當我們在資料表內設定好格式及限制後,之後建立 ViewModel 時就不用再寫 Data Annotation 了。

設計畫面

這裡我只會展示一個表單輸入,模擬資料新增頁面,我直接修改在預設頁面「\Views\Home\Index.cshtml」,先清空原有語法,並貼上以下語法:

這頁面設計了 7 個不同格式的欄位,按下「新增」後就會將資料透過 Ajax 傳送到 Controller。

Controller 接收資料

接著打開「\Controllers\HomeController.cs」,新增 Action 來接收欄位資料。

這個 Action 的重點放在驗證 ModelState.IsValid 的結果是否成功,資料庫的動作我就省略了。

設定 ViewModel 欄位

這裡在「Models」資料夾新增一個 ViewModel 取名為「HomeViewModel」,然後在類別中加入前端要傳入的欄位。

Student 類別的 7 個欄位跟前端 View 設定的欄位名稱是一樣的,但這裡我並沒有指定欄位的名稱、格式、長度等限制,因為格式統一由資料表內設定,這樣就可以做到統一集中管理。
如果要必填的需求,可以再自行增加 [Required] 設定。

Student 類別我繼承了 ModelBase,這是一個新類別,目的是統一驗證欄位格式,當要檢查欄位格式時,就會寫在 ModelBase 裡面。

我有在專案內新增一個資料夾名稱為「ProjectClass」,將 ModelBase 新類別放在 ProjectClass 裡面。

在 ModelBase.cs 類別裡面貼上以下語法:

我所繼承的 IValidatableObject 介面,會在驗證時觸發 Validate 事件,我統一在 Validate 事件內讀取資料表欄位設定,並寫了一些格式檢查範例,你可以自行增加一些格式檢查,其格式名稱只要與資料表欄位 ColumnFormat 相同就行。

新增欄位定義類別

這裡我新增一個新類別 ColumnDefine,一樣放在 ProjectClass 底下,目的為取得資料庫內的設定。

在 ColumnDefine.cs 加入以下語法:

這類別的目的就是取得資料表欄位設定,可是因為太頻繁使用,而且讀取的內容都一樣,所以這裡我加上了 Cache 機制,將讀到的內容先暫存在記憶體內,當一段時間沒人使用時才會釋放,這樣可以提高效能。

新增欄位檢查類別

這裡我將常用的欄位格式檢查邏輯抽離至新類別 FormatCheckUtil 裡面,一樣放在 ProjectClass 底下。

在 FormatCheckUtil 加入以下語法:

讀取 appsettings.json

我將資料庫連線放在 appsettings.json 裡面,打開 appsettings.json 後,加入以下連線字串。

"ConnectionStrings": { "SqlServer": "Data Source=127.0.0.1;Initial Catalog=Teach;Persist Security Info=false;User ID=test;Password=test;" }

安裝 Dapper

我資料庫讀取物件使用微型 ORM 套件 Dapper,需要安裝 Dapper 才能使用。
執行「相依性 > 管理 NuGet 套件」。

執行「相依性 > 管理 NuGet 套件」

搜尋「Dapper」,安裝此套件。

搜尋「Dapper」,安裝此套件

測試驗證

寫到這裡已經完成了後端的檢查,可以按 F5 測試一下專案。
我故意輸入一些錯誤資料,送出後在後端就全部檢查,將所有的錯誤訊息一次顯示。
當有錯誤的欄位,也會帶出中文名稱。

測試驗證

抽離 ModelState.IsValid 驗證

我在 Action 開頭所寫的語法 ModelState.IsValid 它其實是一段固定的語法,在每個 Action 執行前都需要先驗證,因此為求簡化語法,我們可以將此檢查邏輯使用剖面導向程式設計 (AOP) 方式抽離出來,讓 Action 專注在業務邏輯就好。

我在 ProjectClass 底下新增新類別 FilterValidateModel,並繼承 ActionFilterAttribute,然後實作 OnActionExecuting 事件,就可以在 Action 執行前先驗證我們的 Model 輸入內容。

在 FilterValidateModel.cs 加入以下語法:

FilterValidateModel 是一個附加屬性類別,可以在需要時附加在 Action 上面,加上 [FilterValidateModel]

可以在需要時附加在 Action 上面,加上 [FilterValidateModel]

也可以將此附加屬性加入全域註冊,讓所有的 Action 執行前都先執行 Model 驗證。

打開 Program.cs,在 Services 註冊全域屬性:

打開 Program.cs,在 Services 註冊全域屬性

註冊之後,就不用在 Action 上附加屬性 [FilterValidateModel] 了。

以上我的教學並非是主流方式,而是我個人發展出來的專案架構,幫助想要達成統一驗證的人一種思考方式,如果此做法有盲點或是 Bug,歡迎指教,謝謝。

範例下載

連結 GitHub 下載範例

下一篇教學文章

相關學習文章

如果你在學習上有不懂的地方,需要諮詢服務,可以參考站長服務,我想辨法解決你的問題
如果文章內容有過時、不適用或錯誤的地方,幫我在下方留言通知我一下,謝謝

加入社團一起討論

關注我的 IG

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

16 − 1 =


The reCAPTCHA verification period has expired. Please reload the page.