検証

ヒント

この内容は電子ブック『.NET MAUI を使用したエンタープライズ アプリケーション パターン』からの抜粋です。これは .NET Docs で閲覧することも、無料の PDF をダウンロードしてオフラインで読むこともできます。

電子ブック『.NET MAUI を使用したエンタープライズ アプリケーション パターン』の表紙のサムネイル。

ユーザーから入力を受け取るアプリでは、その入力が有効であることを確認する必要があります。 たとえば、アプリでは、入力に特定の範囲の文字のみが含まれているかどうか、入力が一定の長さであるかどうか、または特定の形式に一致しているかどうかを確認します。 検証を行わないと、ユーザーはアプリが失敗する原因となるデータを提供する危険性があります。 適切な検証によって、ビジネス ルールが適用され、攻撃者が悪意のあるデータを挿入するのを防ぐのに役立ちます。

Model-View-ViewModel (MVVM) パターンのコンテキストでは、多くの場合、データの検証を実行して、ユーザーが修正できるように検証エラーをビューに通知するために、ビュー モデルまたはモデルが必要です。 eShop マルチプラットフォーム アプリでは、ビュー モデルのプロパティの同期クライアント側検証を実行し、無効なデータを含むコントロールを強調表示して、データが無効である理由をユーザーに通知するエラー メッセージを表示することで、検証エラーをユーザーに通知します。 次の図は、eShop マルチプラットフォーム アプリでの検証の実行に関連するクラスを示します。

eShop マルチプラットフォーム アプリの検証クラス。

検証を必要とするビュー モデル プロパティは型 ValidatableObject<T> であり、各 ValidatableObject<T> インスタンスには、その Validations プロパティに検証規則が追加されています。 検証は、ValidatableObject<T> インスタンスの Validate メソッドを呼び出すことによってビュー モデルから呼び出されます。これにより、検証規則が取得され、ValidatableObject<T>.Value プロパティに対して実行されます。 検証エラーはすべて ValidatableObject<T> インスタンスの Errors プロパティに配置され、検証が成功したか失敗したかを示すために ValidatableObject<T> インスタンスの IsValid プロパティが更新されます。 次のコードは、ValidatableObject<T> の実装を示します。

using CommunityToolkit.Mvvm.ComponentModel;
namespace eShop.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 プロパティを提供する必要があることを指定します。

次のコード例は、eShop マルチプラットフォーム アプリでモック サービスを使用するときに LoginView でユーザーが入力したユーザー名とパスワードの検証を実行するために使用される IsNotNullOrEmptyRule<T> 検証規則を示します。

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

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

Check メソッドは、値引数が null または空であるかどうか、あるいは空白文字のみで構成されているかどうかを示すブール値を返します。

次のコード例は、eShop マルチプラットフォーム アプリでは使用されませんが、メール アドレスを検証するための検証規則を示します。

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 メソッドは、値引数が有効なメール アドレスであるかどうかを示すブール値を返します。 これは、Regex コンストラクターで指定された正規表現パターンが最初に出現する値引数を検索することによって実現されます。 入力文字列で正規表現パターンが見つかったかどうかは、valueRegex.IsMatch を照合することで判断できます。

Note

プロパティの検証には、依存プロパティが含まれる場合があります。 依存プロパティの一例としては、プロパティ A の有効な値のセットが、プロパティ B で設定された特定の値に依存する場合があります。プロパティ A の値が許可された値の 1 つであることを確認するには、プロパティ B の値を取得する必要があります。さらに、プロパティ B の値が変更された場合、プロパティ A を再検証する必要があります。

検証規則をプロパティに追加する

eShop マルチプラットフォーム アプリでは、検証を必要とするビュー モデル プロパティは型 ValidatableObject<T> として宣言されます。この T は、検証されるデータの型です。 次のコード例は、このような 2 つのプロパティの例を示します。

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

検証を行うには、次のコード例に示すように、検証規則を各 ValidatableObject<T> インスタンスの検証コレクションに追加する必要があります。

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 プロパティの値を指定します。これは、検証が失敗した場合に表示される検証エラー メッセージを指定します。

検証のトリガー

eShop マルチプラットフォーム アプリで使用される検証アプローチでは、プロパティの検証を手動でトリガーし、プロパティが変更されたときに自動的に検証をトリガーできます。

手動による検証のトリガー

ビュー モデル プロパティについては、検証を手動でトリガーできます。 たとえば、モック サービスを使用している場合、ユーザーが LoginViewLogin ボタンをタップすると、eShop マルチプラットフォーム アプリでこれが発生します。 コマンド デリゲートは LoginViewModelMockSignInAsync メソッドを呼び出します。これは、次のコード例に示すように、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 内の両方向のバインドによって UserName または Password プロパティが設定されると、検証がトリガーされます。 次のコード例は、これがどのように発生するかを示します。

<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 インスタンスが追加されています。 この動作によって、Entry で発生する TextChanged イベントに応答して ValidateUserNameCommand が実行されます。これは、Entry 内のテキストが変更されたときに発生します。 次に ValidateUserNameCommand デリゲートによって ValidateUserName メソッドが実行され、これは ValidatableObject<T> インスタンスで Validate メソッドを実行します。 したがって、ユーザーがユーザー名の Entry コントロールに文字を入力するたびに、入力されたデータの検証が実行されます。

検証エラーの表示

eShop マルチプラットフォーム アプリでは、無効なデータを含むコントロールを赤い背景で強調表示し、無効なデータを含むコントロールの下にデータが無効である理由をユーザーに通知するエラー メッセージを表示することで、検証エラーをユーザーに通知します。 無効なデータが修正されると、背景は既定の状態に戻り、エラー メッセージは削除されます。 次の図は、検証エラーが発生した場合の eShop マルチプラットフォーム アプリ内の LoginView を示します。

ログイン中の検証エラーの表示。

無効なデータを含むコントロールの強調表示

.NET MAUI には、検証情報をユーザーに提供するためのさまざまな方法が用意されていますが、最も簡単な方法の 1 つは、Triggers を使用することです。 Triggers を使用すると、コントロールに対して発生するデータの変更に基づいてコントロールの状態 (通常は外観) を変更することができます。 検証では、DataTrigger を使用します。これは、バウンドされたプロパティから発生した変更をリッスンし、その変更に応答します。 LoginViewEntry コントロールは、次のコードを使用してセットアップされます。

<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 オブジェクトが実行されます。 このコントロールには、BackgroundColor プロパティを、StaticResource マークアップを使用して定義されたカスタム カラーに更新する単一の Setter オブジェクトがあります。 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 インスタンスを使用します。

まとめ

eShop マルチプラットフォーム アプリでは、ビュー モデルのプロパティの同期クライアント側検証を実行し、無効なデータを含むコントロールを強調表示して、データが無効である理由をユーザーに通知するエラー メッセージを表示することで、検証エラーをユーザーに通知します。

検証を必要とするビュー モデル プロパティは型 ValidatableObject<T> であり、各 ValidatableObject<T> インスタンスには、その Validations プロパティに検証規則が追加されています。 検証は、ValidatableObject<T> インスタンスの Validate メソッドを呼び出すことによってビュー モデルから呼び出されます。これにより、検証規則が取得され、ValidatableObject<T> Value プロパティに対して実行されます。 検証エラーはすべて ValidatableObject<T> インスタンスの Errors プロパティに配置され、検証が成功したか失敗したかを示すために ValidatableObject<T> インスタンスの IsValid プロパティが更新されます。