在軟體開發中,處理使用者輸入是一個常見的任務,同時也時常伴隨著數據驗證的需求。
在 WPF 或其他 Windows 平台的 MVVM 架構中,要完成數據驗證,可以選擇使用 ValidationRule
、IDataErrorInfo
和 INotifyDataErrorInfo
這幾個類別或接口。
其中 IDataErrorInfo
和 INotifyDataErrorInfo
的部分為 interface ,不僅要進行實作以外,因 MVVM 架構也需要一併實作 INotifyPropertyChanged
。而在 ViewModel 實作上,綁定的屬性也需要撰寫許多涉及 Property 的程式碼,使的開發的過程稍嫌繁瑣。
本篇的主題, CommunityToolkit.Mvvm 套件提供了實作 MVVM 架構時所需要的物件,其中的一個部分便是關於數據驗證,其中提供的物件,不只實作了 INotifyDataErrorInfo
與 INotifyPropertyChanged
等, MVVM 所需實現的interface外,同時也有提供方便的 Attribute 來簡化程式碼。
本次將進行該套件的數據驗證物件並使用 WPF 的示範。
本篇文章為 CommunityToolkit.Mvvm 套件系列的第二部分,若想了解關於該套件的基礎使用,可從前往下列網址前往。
使用ToolKit.MVVM實現ViewModel – Part 1
本次的開發環境,如下
- IDE : Visual Studio 2022
- .NET版本 : . NET 6.0
- Nuget套件: CommunityToolkit.Mvvm 8.2.2
開始使用 ObservableValidator
先從簡單的 View 開始,
View 的 XAML 部分如下,其中包含3個 TextBox 用於輸入資料 , 分別綁定 Name、Phone 和 Email 屬性,以及一個綁定 ShowInfoCommand 的按鈕。
Phone 屬性的 TextBox 設定了 Validation.ErrorTemplate
,當輸入的數值不符合設定的條件時,預期會在該 TextBox 的下方顯示紅色的錯誤訊息。
XAML
1 | <Window.Resources> |
ViewModel 需要使用的 namespace 如下所示,包含 ToolKit.Mvvm 以及 ValidationAttribute 的部分。
1 | using CommunityToolkit.Mvvm.ComponentModel; |
再來則是 ViewModel Window1ViewModelA 的主要程式碼,
繼承部分,
- 繼承套件內的
ObservableValidator
類別,該類別不僅繼承 MVVM 所使用的ObservableObject
,還實作了數據驗證所需的INotifyDataErrorInfo
介面。
Property 部分,
- set 使用 SetProperty 方法來通知 View
- Name 和 Phone 屬性上方,使用了 Required Attribute ,該 Attribute 用於數據驗證,作用是指是該屬性是必填的,不能為 null 或空字串。
Command 部分,
- ShowInfo 方法執行時,首先呼叫
ValidateAllProperties
方法驗證所有的屬性的數據,然後,使用HasErrors
屬性,來確認是否有數據驗證未通過,並顯示內容為 HasErrors 的 MessageBox。若通過驗證的話,則顯示輸入的內容的 MessageBox。
Window1ViewModelA
1 | public partial class Window1ViewModelA : ObservableValidator |
程式執行後,如下圖。
在沒有輸入資料的情況下,按下 ShowInfo 按鈕,便會彈出 MessageBox 並顯示 HasErrors 。
同時可以看到,設定了數據驗證 Attribute [Required] 的屬性,相關的 UI 有了變化,
- Name 的 TextBox 外圍多了紅框,這是因為數據驗證未通過時,TextBox 預設的 ErrorTemplate 是顯示紅框,以提示使用者出現了錯誤狀態。
- Phone 的 TextBox 則與 Name 的不同,下方顯示了驗證錯誤的訊息,有這樣的差異是因為,該 TextBox 有設定了 ErrorTemplate 。
透過上面的範例,已經初步學會如何使用套件中的 ObservableValidator
類別,並成功實現了以下功能:
- 在必填欄位未填寫時,按下執行按鈕後,檢查並提示使用者數據驗證錯誤。
即時驗證數據
那如果想要在輸入數據時,即時進行數據驗證,該如何實現呢?
其中一個方式是使用 ObservableValidator
內提供的 SetProperty 方法。如下面程式碼中,可以看到SetProperty 方法的最後一個參數 ( validate ) 帶入了 true ,這樣會在設定屬性值的同時,自動呼叫內部的驗證方法進行數據驗證。
1 | private string? _name; |
前往 ObservableValidator
類別中的 SetProperty 方法的內容,如下圖,程式首先執行, ObservableObject
的 SetProperty 方法,接著,如果屬性值有變動且 validate 參數為 true 時,將會執行 ValidateProperty
的方法來驗證。
完整程式碼如下。除了在 SetProperty 方法中使用 validate 參數進行驗證,這個驗證只有在屬性的 set 方法被觸發時才會執行。為了在程式一開始時就進行一次驗證,下面程式的建構式裡使用了 ValidateAllProperties
方法,這樣就能在程式啟動後就先觸發數據驗證一次。
Window1ViewModelB
1 | public partial class Window1ViewModelB : ObservableValidator |
替換過ViewModel後的結果如下,當 TextBox 從為空白狀態輸入資料後,錯誤提示便會立即消失,就像在網頁上填寫資料時的效果一樣。
使用 Attribute 簡化程式
到了這個階段,已經能基本使用 ObservableValidator
類別,在 MVVM 架構中進行數據驗證。
那麼 ObservableValidator
類別,是否有像上篇文章中 ObservableObject
類別那樣,能透過 Attribute 來減少程式碼嗎?
答案是可以的。
從一般的寫法,更改為使用套件提供的 Attribute ,變動如下圖所示。紅底部分為原先的一般寫法,綠底則為使用 Attribute 簡化程式後的結果。
- [ObservableProperty] , 如前篇文章所提到的,套件會自動產生,使用 SetProperty 方法的 Property。
- [NotifyDataErrorInfo],此 Attribute 是在
ObservableValidator
中可使用的,等同於上一節中,將 validate 參數設為 true 的效果。 - [Required],用於屬性的數據驗證規則 (此為必填的規則),這部分並不包含在 ToolKit.Mvvm 套件中。
紅底:標準使用
綠底:使用 Attribute
來詳細看一下 NotifyDataErrorInfo Attribute 的文件註解,如下圖。
文件中的解釋,當 ObservableProperty
與 NotifyDataErrorInfo
Attribute 同時使用時,套件自動產生的對應程式碼。
那麼前進到自動產生的程式碼中,如下圖,實際上與 ObservableProperty
Attribute 自動產生的程式,會是差在使用 ValidateProperty
這個方法。
最後附上皆替換為 Attribute 的完整的程式碼。
Window1ViewModelC
1 | public partial class Window1ViewModelC : ObservableValidator |
使用其他的數據驗證 Attribute
在數據驗證方面,.NET 還提供了其他可用的 Attribute。這些 Attribute 不屬於 ToolKit.Mvvm 套件,而是位於 System.ComponentModel.DataAnnotations 命名空間中。
那麼來試試將 ObservableValidator
搭配幾種不同的 Attribute 使用。
View 的 XAML 部分如下,
在 Window.Resources 中建立 TextBoxStyle ,該 Style 設定了 Validation.ErrorTemplate 屬性,視窗裡的4個 TextBox 皆套用此 Style。那 TextBox 分別綁定 Name、Number、Phone 和 Email 屬性。
XAML
1 | <Window.Resources> |
View的樣子,如下圖。
以下是 ViewModel 的程式碼,4 個變數除了使用 [Required] 進行數據驗證外,還各自搭配了不同的 Attribute。
Window2ViewModelA
1 | public partial class Window2ViewModelA : ObservableValidator |
以下是各種 Attribute在實際執行程式時的效果:
Name 變數使用了 MinLength 和 MaxLength Attribute,將數據的長度限制在最小長度和最大長度之間。
Number 變數使用了 Range Attribute, 將數據的數值限制在設定的範圍內。
Phone 變數使用了 Phone Attribute,將會檢查數據的內容是否符合電話格式。
Email 變數使用了 EmailAddress Attribute,將會檢查數據的內容是否符合電子信箱格式。
總結
在 MVVM 架構中實現數據驗證時,使用 CommunityToolkit.Mvvm
套件中的 ObservableValidator
類別是一個不錯的選擇。它可以節省大量時間和功夫,並且可以配合套件內的 Attribute 自動生成程式碼,使程式碼更為精簡,從而讓開發者能更專注於主要的邏輯。
需要注意的是,由於 ObservableValidator
實作了 INotifyDataErrorInfo
介面,因此在 View 上綁定的屬性,在數據驗證後,無論是否通過驗證,數據都會更新到 ViewModel 的屬性中。因此,開發者需要確保在適當的時機觸發數據驗證方法,並且在出現驗證錯誤時,確保程式邏輯能夠阻止執行下一步驟,讓程式正確運作。
而如果希望在 View 層就攔截錯誤數據,使其不進入 ViewModel,那麼實作並使用 ValidationRule
會比較符合這種情況。
另外,本篇的重點著重在 ObservableValidator
類別上,因此未深入探討數據驗證 Attribute。除了本文中介紹的現成屬性外,數據驗證屬性還有一些其他的功能和實作方法,例如:
- 在驗證錯誤時,使用 ErrorMessage 顯示自訂的錯誤訊息
- 使用 CustomValidation Attribute 自訂驗證規則
- 繼承 ValidationAttribute 類別來自訂驗證類別
以上這些內容在網路上已有許多文章介紹。如果有機會,未來會再撰寫一篇番外篇,結合 ObservableValidator
類別,進一步探討這些數據驗證屬性的使用方法。
自言自語543
在撰寫 MVVM 架構時,個人還沒做過太多資料驗證的需求,唯一的幾次經驗則是使用 IDataErrorInfo 並配合較傳統的寫法來實現。
自從使用 CommunityToolkit.Mvvm 套件後,發現了 ObservableValidator 的存在,於是花了些時間了解怎麼使用,說不定未來有機會需要用到。學習的過程中,這才發現原來有數據驗證用的 Attribute ,不但讓程式達成需要的功能、能重複使用以外,還能讓程式碼更精簡,真的是很神奇的東西。
過去,個人在使用 Attribute ,通常是在配合 Winform 的 PropertyGrid 元件時,會在類別的 Property 上添加一些用於在 PropertyGrid 上使用的設定。如今套件中有自動產生程式碼的 Attribute 外,還有數據驗證用的,看來還有很多需要學習的地方呢…