Ant Design Blazorでカスタム検証を使用してみる

BlazorWebAssemblyでAnt Design Blazorを触っています。
Ant Design Blazorは、コンポーネントの数が多く、ドキュメントの内容が濃いので、
お気に入りのUIフレームワークです。見た目もおしゃれで、胸熱です。

既にカスタム検証が実装済の状態で、Ant Design Blazorでどうやって適応させるのかつまずいてしまったので、現段階の解決策を書いてみようと思います。

他にいい方法があれば、コメントで教えてください:bow:

# バージョンなど
- .NET 8.0.204
- Microsoft.AspNetCore.Components.WebAssembly 8.0.4
- AntDesign 0.18.3

# カスタム検証の内容
### 1.カスタム検証
```csharp:CustomValidation.cs
public class RequiredCustomValidation : ValidationAttribute
{
    protected override ValidationResult IsValid(object? value, ValidationContext context)
    {
        if(value is null || string.IsNullOrEmpty(value.ToString()))
        {
            var propertyName = ReflectionHelper.GetPropertyName(context);
            return new ValidationResult($"{propertyName}は必須項目です", new[] { propertyName });
        }

        return ValidationResult.Success!;
    }
}
```

### 2. プロパティ名取得
```csharp:ReflectionHelper.cs
public static class ReflectionHelper
{
    public static string GetPropertyName(ValidationContext context)
    {
        var member = context.ObjectInstance as MemberExpression;
        if (member is null)
        {
            return context.DisplayName;
        }

        var property = member.Member as PropertyInfo;
        if (property is null)
        {
            return context.DisplayName;
        }

        return property.Name;
    }
}
```

# 画面の実装
```html:FormComponent.razor
@page "/form"

<Card>
	<Body>
		<Form Model="UserVM" TModel="User" LabelColSpan="4" WrapperColSpan="16">
			<FormItem>
				<Input @bind-Value="UserVM.Email"></Input>
			</FormItem>
			<FormItem>
				<Input @bind-Value="UserVM.Password"></Input>
			</FormItem>
			<FormItem WrapperColOffset="4" WrapperColSpan="16">
				<Button Type="@ButtonType.Primary">Submit</Button>
			</FormItem>
		</Form>
	</Body>
</Card>
```

```csharp:FormComponent.razor.cs
public partial class FormComponent
{
    private User UserVM { get; set; } = new();

    public class User
    {
        [DisplayName("メールアドレス")]
        public string Email { get; set; }

        [DisplayName("パスワード")]
        public string Password { get; set; }
    }
}
```

# 実装
Ant Design Blazorの導入については、公式ドキュメントに記載があるので割愛します。

### 1.カスタム検証をAnt Design Blazorで使用できるように修正
`IsValid`と`FormatErrorMessage`をAnt Design Blazorで使用できるように関数を分割化しました。
通常の場合は、`IsValid(object? value, ValidationContext context)`が呼ばれる想定です。

```csharp:CustomValidation.cs
public class RequiredCustomValidation : ValidationAttribute
{
    protected override ValidationResult IsValid(object? value, ValidationContext context)
    {
        if(IsValid(value))
        {
            var propertyName = ReflectionHelper.GetPropertyName(context);
            return new ValidationResult(FormatErrorMessage(propertyName));
        }

        return ValidationResult.Success!;
    }

    public override bool IsValid(object? value)
    {
        return value is null || string.IsNullOrEmpty(value.ToString());
    }

    public override string FormatErrorMessage(string name)
    {
        return string.Format("{0}は必須項目です", name);
    }
}
```

### 2.ValidtionRulesの実装
Ant Design Blazorの場合は、プロパティに属性として追加するのではなく、
ルールを定義し、定義したルールを`formItem`に設定することでValidationの設定ができます。

メールアドレスの検証ルールを`RequiredCustomValidation`を呼び出して設定することで、
検証ルールの共通化が図れます。

```csharp:FormComponent.razor.cs
    private FormValidationRule[] emailRules = new FormValidationRule[]{
    new FormValidationRule { Validator = (validationContext) => {
            var validationAttribute = new RequiredCustomValidation();

            if (validationAttribute.IsValid(validationContext.Value))
            {
                string errorMessage = validationAttribute.FormatErrorMessage(validationContext.DisplayName);

                var result = new ValidationResult(errorMessage, new string[] { validationContext.FieldName });

                return result;
            }

            return null;
        }
    },
};
```

formに`ValidateMode="@FormValidateMode.Rules"`を設定することで、
検証ルールでvalidationを行うことを設定できます。
formItemに先ほど作成した`emailRules`を設定し、準備は完了です。
```html:FormComponent.razor
@page "/form"

<Card>
	<Body>
		<Form Model="UserVM" TModel="User" LabelColSpan="4" WrapperColSpan="16" ValidateMode="@FormValidateMode.Rules">
			<FormItem Rules="@(emailRules)">
				<Input @bind-Value="UserVM.Email"></Input>
			</FormItem>
			<FormItem>
				<Input @bind-Value="UserVM.Password"></Input>
			</FormItem>
			<FormItem WrapperColOffset="4" WrapperColSpan="16">
				<Button Type="@ButtonType.Primary" HtmlType="submit">Submit</Button>
			</FormItem>
		</Form>
	</Body>
</Card>
```

# 結果
下記のように、submitボタンクリック時に検証が走り、
検証ルールに設定した検証が実行されていることを確認しました。
![スクリーンショット 2024-05-06 192419.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2647017/015b807d-8d50-309e-095e-f4f24d91d07f.png)

# 補足
formに`ValidateMode="@FormValidateMode.Rules"`と設定すると、Attribute属性にRequiredを設定していても、必須マークが設定されなくなってしまいます。
そのため、formItemに`Required`の設定をして必須マークを付与してください。

```html:FormComponent.razor
<FormItem Rules="@(emailRules)" Required>
	<Input @bind-Value="UserVM.Email"></Input>
</FormItem>
```

# 最後に
フロントエンドとサーバーサイドで検証ルールを共通化できるよう、
UIフレームワークでも検証ルールの設定方法を考えてみました。

やっていることは、とても単純なのですが、
一度沼にはまると、なかなか思いつかなかったので、
今後のためにも記録していきたいと思います。

まだまだ初心者のため、その他最善策などありましたらコメントお願いします！

# 参考にしたリンク
- https://antblazor.com/en-US/