驗證

提示

本內容節錄自《Enterprise Application Patterns Using .NET MAUI》電子書,可以從 .NET Docs 取得,也可以免費下載 PDF 離線閱讀。

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

任何接受使用者輸入的應用程式都應該確保輸入的內容有效。 例如,應用程式可以檢查輸入內容是否只包含特定範圍的字元、有特定的長度或符合特定的格式。 如不經驗證,使用者可能會提供導致應用程式失敗的資料。 正確的驗證會強制執行商務規則,並有助於防止攻擊者插入惡意資料。

在 Model-View-ViewModel (MVVM) 模式的內容中,通常需要有檢視模型或模型才能執行資料驗證,並向檢視發出任何驗證錯誤訊號,以便使用者更正錯誤。 eShopOnContainers 多平台應用程式會執行檢視模型屬性的同步用戶端驗證,並會醒目提示包含無效資料的控制項,以及顯示通知使用者資料無效原因的錯誤訊息,藉以通知使用者任何驗證錯誤。 下圖顯示在 eShopOnContainers 多平台應用程式中執行驗證時所涉及的類別。

Validation classes in the eShopOnContainers multi-platform app.

需要驗證的檢視模型屬性類型為 ValidatableObject<T>,而且每個 ValidatableObject<T> 執行個體都已將驗證規則新增至其 Validations 屬性。 藉由呼叫 ValidatableObject<T> 執行個體的 Validate 方法,從檢視模型叫用驗證,這會擷取驗證規則,並針對 ValidatableObject<T>.Value 屬性執行。 所有驗證錯誤都放在 ValidatableObject<T> 執行個體的 Errors 屬性中,而且 ValidatableObject<T> 執行個體的 IsValid 屬性會更新,以指出驗證成功或失敗。 下列程式碼示範 ValidatableObject<T> 的實作:

using CommunityToolkit.Mvvm.ComponentModel;
namespace eShopOnContainers.Validations;
public class ValidatableObject<T> : ObservableObject, IValidity
{
    private IEnumerable<string> _errors;
    private bool _isValid;
    private T _value;
    public List<IValidationRule<T>> Validations { get; } = new();
    public IEnumerable<string> Errors
    {
        get => _errors;
        private set => SetProperty(ref _errors, value);
    }
    public bool IsValid
    {
        get => _isValid;
        private set => SetProperty(ref _isValid, value);
    }
    public T Value
    {
        get => _value;
        set => SetProperty(ref _value, value);
    }
    public ValidatableObject()
    {
        _isValid = true;
        _errors = Enumerable.Empty<string>();
    }
    public bool Validate()
    {
        Errors = Validations
            ?.Where(v => !v.Check(Value))
            ?.Select(v => v.ValidationMessage)
            ?.ToArray()
            ?? Enumerable.Empty<string>();
        IsValid = !Errors.Any();
        return IsValid;
    }
}

屬性變更通知是由 ObservableObject 類別提供,因此 Entry 控制項可以繫結至檢視模型類別之 ValidatableObject<T> 執行個體的 IsValid 屬性,以接獲輸入資料是否有效的通知。

指定驗證規則

驗證規則是透過建立衍生自 IValidationRule<T> 介面的類別所指定,如下列程式碼範例所示:

public interface IValidationRule<T>
{
    string ValidationMessage { get; set; }
    bool Check(T value);
}

這個介面指定驗證規則類別必須提供用以執行必要驗證的布林 Check 方法,而其值為驗證錯誤訊息的 ValidationMessage 屬性,則會在驗證失敗時出現。

下列程式碼範例會顯示 IsNotNullOrEmptyRule<T> 驗證規則,用以在 eShopOnContainers 多平台應用程式中使用模擬服務時,驗證使用者在 LoginView 中輸入的使用者名稱和密碼:

public class IsNotNullOrEmptyRule<T> : IValidationRule<T>
{
    public string ValidationMessage { get; set; }

    public bool Check(T value) =>
        value is string str && !string.IsNullOrWhiteSpace(str);
}

Check 方法會傳回布林值,指出 value 引數為 Null、空白或只包含空白字元。

雖然 eShopOnContainers 多平台應用程式不使用下列程式碼,但此範例仍會顯示驗證電子郵件地址的驗證規則:

public class EmailRule<T> : IValidationRule<T>
{
    private readonly Regex _regex = new(@"^([w.-]+)@([w-]+)((.(w){2,3})+)$");

    public string ValidationMessage { get; set; }

    public bool Check(T value) =>
        value is string str && _regex.IsMatch(str);
}

Check 方法會傳回布林值,指出 value 引數是否為有效的電子郵件地址。 在 value 引數中搜尋第一次出現的規則運算式模式,即可達成此目的,此模式是在 Regex 建構函式中指定。 針對 Regex.IsMatch 檢查 value 即可判斷能否在輸入字串中找到規則運算式模式。

注意

屬性驗證有時會牽涉到相依屬性。 例如,當屬性 A 的有效值集合相依於屬性 B 中曾設定的特定值時,這就是相依屬性。若要檢查屬性 A 的值是否為其中一個允許的值,則牽涉到擷取屬性 B 的值。此外,當屬性 B 的值變更時,屬性 A 也必須重新驗證。

將驗證規則新增至屬性

在 eShopOnContainers 多平台應用程式中,需要驗證的檢視模型屬性會宣告為 ValidatableObject<T> 類型,其中 T 是要驗證的資料類型。 下列程式碼範例會舉例兩個此類屬性:

public ValidatableObject<string> UserName { get; private set; }
public ValidatableObject<string> Password { get; private set; }

若要執行驗證,您必須將驗證規則新增至每個 ValidatableObject<T> 執行個體的 Validations 集合,如以下程式碼範例所示:

private void AddValidations()
{
    UserName.Validations.Add(new IsNotNullOrEmptyRule<string> 
    { 
        ValidationMessage = "A username is required." 
    });

    Password.Validations.Add(new IsNotNullOrEmptyRule<string> 
    { 
        ValidationMessage = "A password is required." 
    });
}

這個方法會將 IsNotNullOrEmptyRule<T> 驗證規則新增至每個 ValidatableObject<T> 執行個體的 Validations 集合,指定驗證規則的 ValidationMessage 屬性值,這會指定驗證失敗時所顯示的驗證錯誤訊息。

觸發驗證

eShopOnContainers 多平台應用程式中使用的驗證方法可以手動觸發屬性驗證,並在屬性變更時自動觸發驗證。

手動觸發驗證

您可以手動觸發檢視模型屬性的驗證。 例如,在使用模擬服務時,當使用者點選 LoginView 中的 Login 按鈕,eShopOnContainers 多平台應用程式就會觸發驗證。 命令委派會呼叫 LoginViewModel 中的 MockSignInAsync 方法,這會執行 Validate 方法以叫用驗證,如下列程式碼範例所示:

private bool Validate()
{
    bool isValidUser = ValidateUserName();
    bool isValidPassword = ValidatePassword();
    return isValidUser && isValidPassword;
}

private bool ValidateUserName()
{
    return _userName.Validate();
}

private bool ValidatePassword()
{
    return _password.Validate();
}

Validate 方法透過對每個 ValidatableObject<T> 執行個體叫用 Validate 方法,驗證使用者在 LoginView 中輸入的使用者名稱和密碼。 下列程式碼範例顯示 ValidatableObject<T> 類別中的 Validate 方法:

public bool Validate()
{
    Errors = _validations
        ?.Where(v => !v.Check(Value))
        ?.Select(v => v.ValidationMessage)
        ?.ToArray()
        ?? Enumerable.Empty<string>();

    IsValid = !Errors.Any();

    return IsValid;
}

這個方法會擷取新增至物件 Validations 集合的任何驗證規則。 每個擷取驗證規則的 Check 方法都會被執行,而無法驗證資料之任何驗證規則的 ValidationMessage 屬性值都會新增至 ValidatableObject<T> 執行個體的 Errors 集合。 最後設定 IsValid 屬性,並將其值傳回給呼叫方法,指出驗證成功或失敗。

當屬性變更時觸發驗證

每當繫結的屬性變更時,也會自動觸發驗證。 例如,當 LoginView 中的雙向繫結設定 UserNamePassword 屬性時,就會觸發驗證。 下列程式碼範例會示範觸發流程:

<Entry Text="{Binding UserName.Value, Mode=TwoWay}">
    <Entry.Behaviors>
        <behaviors:EventToCommandBehavior
            EventName="TextChanged"
            Command="{Binding ValidateUserNameCommand}" />
    </Entry.Behaviors>
</Entry>

Entry 控制項繫結至 ValidatableObject<T> 執行個體的 UserName.Value 屬性,而控制項的 Behaviors 集合中則新增了 EventToCommandBehavior 執行個體。 這個行為執行 ValidateUserNameCommand 以回應 Entry 中引發的 TextChanged 事件,這是當 Entry 中的文字變更時所引發。 接著,ValidateUserNameCommand 委派會執行 ValidateUserName 方法,在 ValidatableObject<T> 執行個體上執行 Validate 方法。 因此,每當使用者在 Entry 控制項中輸入使用者名稱的字元時,就會對輸入資料執行驗證。

顯示驗證錯誤

eShopOnContainers 多平台應用程式通知使用者任何驗證錯誤的方式為:以紅色背景醒目提示包含無效資料的控制項,並在包含無效資料的控制項下方,顯示通知使用者資料無效原因的錯誤訊息。 更正無效資料時,背景會變更回預設狀態,並移除錯誤訊息。 下圖顯示出現驗證錯誤時,eShopOnContainers 多平台應用程式中的 LoginView

Displaying validation errors during login.

醒目提示包含無效資料的控制項

.NET MAUI 提供數種方式向終端使用者顯示驗證資訊,其中最直接的一種方式是使用 TriggersTriggers 根據控制項發生的事件或資料變更,為我們提供了變更控制項狀態的方法,通常是變更外觀。 為了驗證,我們將使用 DataTrigger 以接聽從繫結屬性引發的變更,並回應變更。 LoginView 中的 Entry 控制項是使用下列程式碼所設定:

<Entry Text="{Binding UserName.Value, Mode=TwoWay}">
    <Entry.Style>
        <OnPlatform x:TypeArguments="Style">
            <On Platform="iOS, Android" Value="{StaticResource EntryStyle}" />
            <On Platform="WinUI" Value="{StaticResource WinUIEntryStyle}" />
        </OnPlatform>
    </Entry.Style>
    <Entry.Behaviors>
        <mct:EventToCommandBehavior
            EventName="TextChanged"
            Command="{Binding ValidateCommand}" />
    </Entry.Behaviors>
    <Entry.Triggers>
        <DataTrigger 
            TargetType="Entry"
            Binding="{Binding UserName.IsValid}"
            Value="False">
            <Setter Property="BackgroundColor" Value="{StaticResource ErrorColor}" />
        </DataTrigger>
    </Entry.Triggers>
</Entry>

DataTrigger 指定下列屬性:

屬性 說明
TargetType 觸發程序所屬的控制項類型。
Binding 資料 Binding 標記會提供變更通知和觸發條件的值。
Value 符合觸發條件時要指定的資料值。

針對這個 Entry,我們將接聽 LoginViewModel.UserName.IsValid 屬性的變更。 每次這個屬性引發變更時,將會比較此值與 DataTrigger 中設定的 Value 屬性。 如果兩值相等,即符合觸發條件,並執行提供給 DataTrigger 的任何 Setter 物件。 這個控制項只有一個 Setter 物件,其可將 BackgroundColor 屬性更新為使用 StaticResource 標記定義的自訂色彩。 不再符合 Trigger 條件時,控制項會將 Setter 物件所設定的屬性還原為到之前的狀態。 如需 Triggers 的詳細資訊,請參閱 .NET MAUI Docs:觸發程序

顯示錯誤訊息

UI 會在每個資料驗證失敗之控制項下方的 Label 控制項中顯示驗證錯誤訊息。 如果使用者尚未輸入有效的使用者名稱,下列程式碼範例會顯示可顯示驗證錯誤訊息的 Label

<Label
    Text="{Binding UserName.Errors, Converter={StaticResource FirstValidationErrorConverter}"
    Style="{StaticResource ValidationErrorLabelStyle}" />

每個 Label 都會繫結至正在驗證之檢視模型物件的 Errors 屬性。 Errors 屬性是由 ValidatableObject<T> 類別提供,且類型為 IEnumerable<string>。 因為 Errors 屬性可以包含多個驗證錯誤,所以使用 FirstValidationErrorConverter 執行個體擷取集合中的第一個錯誤以供顯示。

摘要

eShopOnContainers 多平台應用程式會執行檢視模型屬性的同步用戶端驗證,並會醒目提示包含無效資料的控制項,以及顯示通知使用者資料無效原因的錯誤訊息,藉以通知使用者任何驗證錯誤。

需要驗證的檢視模型屬性類型為 ValidatableObject<T>,而且每個 ValidatableObject<T> 執行個體都已將驗證規則新增至其 Validations 屬性。 藉由呼叫 ValidatableObject<T> 執行個體的 Validate 方法,從檢視模型叫用驗證,這會擷取驗證規則,並針對 ValidatableObject<T> Value 屬性執行。 所有驗證錯誤都放在 ValidatableObject<T> 執行個體的 Errors 屬性中,而且 ValidatableObject<T> 執行個體的 IsValid 屬性會更新,以指出驗證成功或失敗。