概要
Blazorにおけるフォームバリデーションの手法に関して紹介します。
下記のようなログインフォームを例にして紹介します。
前提
.NET Core SDK 3.1.100-preview3-014645
Microsoft.AspNetCore.Blazor 3.1.0-preview2.19528.8
Visual Studio 2019
WebAssembly版(Client版)を使用しています。
また、サンプルではUI要素としてMatBlazorを使用しています。
詳細は下記を参照してください。
https://qiita.com/nobu17/items/ecf2121f7bbb6bc5294b
MatBlazorを使わない場合、一般的なForm要素に置き換えてください。
MatTextField → InputTextもしくはinput
MatButton → button (type="submit")
基本編
一番多く使う基本的なパターンの実装と説明を行います。
入力モデルの作成
まずは、入力画面にバインドするクラスを定義します。
その上で、各項目に対するバリデーション定義を追加します。
バリデーションを属性で表現するのは、ASP.NET MVC等でもおなじみな方法なので.NET開発者であれば見慣れたものかと思います。
publicclassLoginData{[Required(ErrorMessage="ユーザIDを入力してください。")][StringLength(16,ErrorMessage="ユーザIDが長すぎます。")]publicstringUserID{get;set;}[Required(ErrorMessage="パスワードを入力してください。")][StringLength(32,ErrorMessage="パスワードが長すぎます。")]publicstringPassword{get;set;}}
コードビハインドの作成
次にViewにバインドする、先ほどのLoginDataをメンバとして保持するクラスを作成します。
今回はコードビハインドでrazorコンポーネントとC#コードを分離して記載します。
コードビハインドに関しての詳細は、下記を参照してください。
https://qiita.com/nobu17/items/b7dc78db7beb1d833dc8
publicclassForm1ViewModel:ComponentBase{publicLoginDataLoginData{get;set;}=newLoginData();publicvoidSubmit(){// do something}}
ビューの定義
コードビハインドとバインドする画面を作成します。
@inherits MatTest.Models.Form.Form1ViewModel
<EditFormModel="@LoginData"OnValidSubmit="@Submit"><DataAnnotationsValidator/><ValidationSummary/><MatTextFieldFullWidth="true"Label="UserID"@bind-Value="@LoginData.UserID"></MatTextField><ValidationMessageFor="@(() => LoginData.UserID)"/><MatTextFieldFullWidth="true"Label="Password"@bind-Value="@LoginData.Password"Type="password"></MatTextField><ValidationMessageFor="@(() => LoginData.Password)"/><MatButtonLabel="Login"Outlined="true"Type="submit"></EditForm>
EditForm
フォームで入力する要素をこのタグで囲みます。
- Modelにフォームに入力するプロパティをバインドします。
- OnValidSubmitに入力が正常な場合の確定処理のメソッドをバインドします。
また、不正な入力で確定ボタン押下を検知したい場合には、OnInvalidSubmitイベントをバインドすることで検知できます。
DataAnnotationsValidator
先ほど入力データクラスに付与した属性(Requiredなど)のバリデーションを実施する場合に記載します。
ValidationSummary
バリデーションで発生したエラー内容を表示します。
ValidationMessage
ValidationSummaryはすべてのエラー内容が表示されるため、
各入力項目に対して個別のバリデーションを表示したい場合に使用します。
For内にラムダでプロパティを指定します。
カスタムの検証属性
独自のバリデーション属性を作成したい場合は、ValidationAttributeクラスを継承します。
これはBlazor固有というよりも.NETでは一般的に使われている手法です。
publicclassCustomeValidationAttribute:ValidationAttribute{protectedoverrideValidationResultIsValid(objectvalue,ValidationContextvalidationContext){varstr=valueasstring;if(str!=null&&string.IsNullOrWhiteSpace(str)){returnnewValidationResult("空白は無効です。",new[]{validationContext.MemberName});}returnValidationResult.Success;}}
IsValidをオーバーライドして対象のオブジェクトを検証します。
- 正常の場合はValidationResult.Success戻す
- 入力エラーの場合は、ValidationResult内にエラーメッセージとvalidationContext.MemberNameを入れて戻す
入力エラー時には下記のようにエラーメッセージが表示されます。
応用編
基本をベースに、色々な場合を紹介します。
ネストしたクラスに対するバリデーション
DataAnnotationsValidatorはネストしたオブジェクトに対しては、機能しません。
現在はまだプレビュー版となりますが、下記の手順で可能となります。
モジュール追加
Nugetから下記モジュールを追加します。
Microsoft.AspNetCore.Blazor.DataAnnotations.Validation
属性追加
ネストしたクラスのプロパティに対して、ValidateComplexType属性を付与します。
publicclassNestedData{[Required][ValidateComplexType]publicLoginDataLoginData{get;set;}=newLoginData();}
バリデータの変更
DataAnnotationsValidatorの代わりにObjectGraphDataAnnotationsValidatorを使用します。
@inherits MatTest.Models.Form.Form2ViewModel
<EditFormModel="@NestedData"OnValidSubmit="@Submit"><ObjectGraphDataAnnotationsValidator/><ValidationSummary/><MatTextFieldFullWidth="true"Label="UserID"@bind-Value="@NestedData.LoginData.UserID"></MatTextField><MatTextFieldFullWidth="true"Label="Password"@bind-Value="@NestedData.LoginData.Password"Type="password"></MatTextField><MatButtonLabel="Login"Outlined="true"Type="submit"></EditForm>
OSSモジュール(FluentValidation)を使ったカスタムバリデーション
OSSで提供されいてる機能で属性検証以外のバリデーションを作成できます。
FluentValidationといった.NET向けのバリデーションライブラリをBlazor対応させる方法が紹介されているのでそちらを参考に実装します。
https://chrissainty.com/using-fluentvalidation-for-forms-validation-in-razor-components/
パッケージの導入
NugetからFluentValidationをインストールします。
バリデータの作成
FluentValidationの作法に沿ったバリデーションクラスを作成します。
publicclassLoginDataValidator:AbstractValidator<LoginData>{publicLoginDataValidator(){RuleFor(p=>p.UserID).NotEmpty().WithMessage("ログインIDを入力してください。");RuleFor(p=>p.UserID).MaximumLength(10).WithMessage("ログインIDは10文字まで入力してください。");RuleFor(p=>p.Password).NotEmpty().WithMessage("パスワードを入力してください。");RuleFor(p=>p.Password).MaximumLength(10).WithMessage("パスワードは10文字まで入力してください。");}}
AbstractValidatorを継承したクラスを作成します。
(ジェネリクスにはバリデーション対象のクラスを指定)
コンストラクタ内でRuleForラムダで各メンバーのバリデーションを実装します。
コンポーネントの作成
作成したバリデータだけではBlazorではそのまま使えないため、Blazor側のバリデーションに対応させるためのコンポーネントを作成します。
BlazorにはバリデーションのためのEditContextといった仕組みが提供されており、その仕組み内でFluentValidationのバリデーションを行います。
EditContextの詳細に関しては割愛しますが、下記等が参考になります。
https://gunnarpeipman.com/blazor-form-validation/
掲載元(掲載時より、一部APIの仕様が変わっているのでその対応を行っています。AddRangeをAddに変更。参考)
publicclassFluentValidationValidator:ComponentBase{[CascadingParameter]EditContextCurrentEditContext{get;set;}protectedoverridevoidOnInitialized(){if(CurrentEditContext==null){thrownewInvalidOperationException($"{nameof(FluentValidationValidator)} requires a cascading "+$"parameter of type {nameof(EditContext)}. For example, you can use {nameof(FluentValidationValidator)} "+$"inside an {nameof(EditForm)}.");}CurrentEditContext.AddFluentValidation();}}publicstaticclassEditContextFluentValidationExtensions{publicstaticEditContextAddFluentValidation(thisEditContexteditContext){if(editContext==null){thrownewArgumentNullException(nameof(editContext));}varmessages=newValidationMessageStore(editContext);editContext.OnValidationRequested+=(sender,eventArgs)=>ValidateModel((EditContext)sender,messages);editContext.OnFieldChanged+=(sender,eventArgs)=>ValidateField(editContext,messages,eventArgs.FieldIdentifier);returneditContext;}privatestaticvoidValidateModel(EditContexteditContext,ValidationMessageStoremessages){varvalidator=GetValidatorForModel(editContext.Model);varvalidationResults=validator.Validate(editContext.Model);messages.Clear();foreach(varvalidationResultinvalidationResults.Errors){messages.Add(editContext.Field(validationResult.PropertyName),validationResult.ErrorMessage);}editContext.NotifyValidationStateChanged();}privatestaticvoidValidateField(EditContexteditContext,ValidationMessageStoremessages,inFieldIdentifierfieldIdentifier){varproperties=new[]{fieldIdentifier.FieldName};varcontext=newValidationContext(fieldIdentifier.Model,newPropertyChain(),newMemberNameValidatorSelector(properties));varvalidator=GetValidatorForModel(fieldIdentifier.Model);varvalidationResults=validator.Validate(context);messages.Clear(fieldIdentifier);// APIの仕様変更のため、Addに変更//messages.AddRange(fieldIdentifier, validationResults.Errors.Select(error => error.ErrorMessage));messages.Add(fieldIdentifier,validationResults.Errors.Select(error=>error.ErrorMessage));editContext.NotifyValidationStateChanged();}privatestaticIValidatorGetValidatorForModel(objectmodel){varabstractValidatorType=typeof(AbstractValidator<>).MakeGenericType(model.GetType());varmodelValidatorType=Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(t=>t.IsSubclassOf(abstractValidatorType));varmodelValidatorInstance=(IValidator)Activator.CreateInstance(modelValidatorType);returnmodelValidatorInstance;}}
実装
作成したバリデータコンポーネント(FluentValidationValidator)を配置します。
<EditFormModel="@LoginData"OnValidSubmit="@HandleValidSubmit"class="mat-layout-grid-cell mat-layout-grid-cell-span-12"><FluentValidationValidator/><ValidationSummary/><MatTextFieldFullWidth="true"Label="UserID"@bind-Value="@LoginData.UserID"></MatTextField><MatTextFieldFullWidth="true"Label="Password"@bind-Value="@LoginData.Password"Type="password"></MatTextField><MatButtonLabel="Login"Outlined="true"Type="submit"></MatButton></EditForm>
まとめ
Blazorにおけるフォームバリデーション手法に関してまとめました。
バリデーション手法は従来の.NETの手法を踏襲しているため、親しみがある人も多いのではないでしょうか。
Blazorのその他の投稿記事
何点かBlazorに関して記事を書いていますので、良ければ見てみてください。
- Blazor向けのUIフレームワークのMatBlazorを使ってみる
- Blazorの初期読み込み画面(Loading)を変更する
- Blazorで未ログイン時にログインページにリダイレクトする
- Blazorでコードビハインドでロジックとビューを分離して記述する
- Blazor向けのUIフレームワークのRadzen.Blazorを使ってみる
- Blazorで作成したウェブサイトをGitHub Pagesで公開する
- Blazorで作成したウェブサイトをFirebaseで公開する
参考資料
https://docs.microsoft.com/ja-jp/aspnet/core/blazor/forms-validation?view=aspnetcore-3.0
https://gunnarpeipman.com/blazor-form-validation/
https://chrissainty.com/using-fluentvalidation-for-forms-validation-in-razor-components/
https://blazor-university.com/forms/writing-custom-validation/
http://blazorhelpwebsite.com/Blog/tabid/61/EntryId/4337/Blazor-Forms-and-Validation.aspx
https://remibou.github.io/Client-side-validation-with-Blazor-and-Data-Annotations/
https://dzone.com/articles/blazor-form-validation
https://itnext.io/blazor-forms-and-validation-418173350435