クライアント側にVue.jsをSPA(シングルページアプリケーション)ではない方法で利用することについて記述していきます。
全体
・Vue.jsとblumaの設定
・cssをblumaに変更
・バリデーションの変更(本稿)
・Vue.jsでのコンポーネントの利用
-バリデーションの変更-
環境
「ASp.Net Core でPostgreSQLを利用してIdentityで認証を使えるようにする」で作成した環境にVue.jsを組み込み、cssフレームワークをblumaに変更します。
- VisualStudio2019 Ver.16.2.3
- ASP.NET Core 2.2
- PostgreSQL 9.6 インストール済み、接続用のアカウント作成済み
- EntityFramework
- Vue.js 2.6.10
- bluma 0.7.5
バリデーションをどうするか
前回までの作業でページから「Bootstrap」と「JQuery」を外して「bluma」を利用するまでを作りました。その結果、もともとJQueryで作られていたと思われるバリデーション機能が動作しなくなっています。
クライアントでHTML5の機能で実行してもいいかと思っていたのですが、画面から操作している分には問題が無くとも「POST」を直接実行することがあるかもしれないので、サーバーと同じバリデーションが実行できるようにHTML5用のデータアノテーションを追加することにしました。
データアノテーション(バリデーション用アトリビュート)の作成
プロジェクト配下に「Authorization」を作成し、そこに以下のクラスを作ります。
ちなみに既存の「Required」と区別するために「RequiredH5」としています。
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace WebApplication1.Attributes
{
/// <summary>
/// RequiredバリデーションのHTML5対応のアトリビュート
/// </summary>
public class RequiredH5Attribute : ValidationAttribute, IClientModelValidator
{
string _errorMessage = "この項目は入力必須です。";
/// <summary>
/// コンストラクタ(引数無)
/// </summary>
public RequiredH5Attribute() {
}
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="errorMessageAccessor">エラーメッセージへのアクセサ</param>
public RequiredH5Attribute(Func<string> errorMessageAccessor) : base(errorMessageAccessor)
{
_errorMessage = errorMessageAccessor();
}
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="errorMessage">エラーメッセージ</param>
public RequiredH5Attribute(string errorMessage) : base(errorMessage)
{
_errorMessage = errorMessage;
}
/// <summary>
/// バリデーション(サーバーサイド)
/// </summary>
/// <param name="value">値</param>
/// <param name="validationContext">バリデーションコンテキスト</param>
/// <returns></returns>
protected override ValidationResult IsValid(
object value, ValidationContext validationContext)
{
if (value.ToString().Trim().Length > 0)
{
// 入力の桁数(トリムしたあと)が1以上なら正常
return ValidationResult.Success;
}
{
// 入力の桁数(トリムしたあと)が0以下なら異常
return new ValidationResult(_errorMessage);
}
}
/// <summary>
/// クライアントでのバリデーション用の操作
/// </summary>
/// <param name="context">クライアントのバリデーションコンテキスト</param>
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// タグに「required="required"」と「required-err-msg="<エラーメッセジ>"」を設定する
MergeAttribute(context.Attributes, "required", "required");
MergeAttribute(context.Attributes, "required-err-msg", _errorMessage);
}
/// <summary>
/// アトリビュートの結合
/// </summary>
/// <param name="attributes">アトリビュート</param>
/// <param name="key">追加するキー</param>
/// <param name="value">追加する値</param>
/// <returns></returns>
private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
}
クライアントの操作をするには以前はアダプターを作ってサービスに追加するような作業が必要だった様ですが、今は「IClientModelValidator」を継承して「AddValidation」を実装するといいようです。
コンストラクタはデフォルトのメッセージを表示する引数無のコンストラクタと、エラーメッセージを引数とするコンストラクタを作っています。
サーバー側のバリデーションは「IsValid」をオーバーライドして作成します。
クライアント側のバリデーションというか、属性の追加は「AddValidation」で行います。「required」属性に「required」を設定し、「required-err-msg」にエラーメッセージを設定しています。「required」はHTML5の属性ですが、「required-err-msg」は独自の属性です。HTML5では残念ながらエラーメッセージは属性として設定できず、後述するjavascriptの「setCustomValidity」メソッドで設定する必要があります。
クライアントのバリデーションメッセージを変更する
HTML5のバリデーションは、制約自身は属性で追加できますが、メッセージの変更にはJavaScriptの利用が必要になります。
「wwwroot/js/vaidationH5.js」を以下のように作成します。
// クライアント側で「input」タグのバリデーションメッセージを変更するために
// 「invalid」イベントでメッセージを設定するイベントハンドラーを設定
window.onload = function () {
// inputタグのエレメントをすべて取得
var inputList = document.getElementsByTagName('input');
for (var i = 0; i < inputList.length; i ++) {
// 全てのinputエレメントの「invalid」イベントにバリデーションチェックエラーの場合のエラーメッセージを設定
inputList[i].addEventListener('invalid', function (e) {
if (e.target.validity.valueMissing) {
// Requiredの場合
e.target.setCustomValidity(e.target.getAttribute("required-err-msg"));
}
else {
e.target.setCustomValidity("");
}
// elseの前にエラー情報に対するエラーメッセージを追加する予定
}, false);
}
// 全てのバリデーションサマリーのエレメントを取得
var summaryErrorElements = document.getElementsByClassName('validation-summary-valid');
for (i = 0; i < summaryErrorElements.length; i++) {
// サマリー内はリストになっていて、「display=none」しかなければサマリーは非表示、以外のものが有ればサマリーを表示。
var summaryElements = summaryErrorElements[i].getElementsByTagName('li');
var existFlag = false;
for (var j = 0; j < summaryElements.length; j ++) {
if (summaryElements[j].style.display !== 'none') {
summaryErrorElements[i].style.display = 'block';
existFlag = true;
}
}
if (existFlag === false) summaryErrorElements[i].style.display = 'none';
}
};
フォーム読み込み完了時に「input」タグのすべてに対して、「invalid」イベントのハンドラーを設定しています。現在Requiredのみ記述していますが、残りのバリデーションの対応は同じようにできるはずです。ちなみにバリデーションの条件(validityのメンバ)は以下のようになっているようです。
- valueMissing => 未入力(属性:required)
- typeMismatch => typeの不一致(type属性に依存)
- patternMismatch => 入力パターンの不一致(属性:pattern)
- tooLong => 文字列の最大長を超えている(属性:maxlength )
- tooShort => 文字列の最小長より小さい(属性:minlength )
- rangeOverflow => 数値や時刻等の最大値を超えている(属性:max )
- rangeUnderflow => 数値や時刻等の最小値より小さい(属性:min )
- step => 数値や時刻等の値の刻み単位が一致しない(属性:step)
「asp-validation-summary」の対応もこの中で行っていて、サマリーが有れば表示しなければ非表示にしています。ただ、この処理はもう少し修正が必要かもしれません。
最終的にできたものはいずれGitHubに公開するつもりです。
この処理を追加するのに「Pages/Shared/_Layout.cshtml」の最後にスクリプトを追加しておきます。
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication1</title>
<environment include="Development">
<link rel="stylesheet" href="~/css/bulma.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.1.2/css/bulma.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js" integrity="sha256-ufGElb3TnOtzl5E4c/qQnZFGP+FYEZj5kbSEdJNrw0A=" crossorigin="anonymous"></script>
</environment>
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar has-background-info">
<div class="navbar-brand">
<div class="navbar-item title">
WebApplication1
</div>
</div>
<div class="navbar-menu is-active is-tab">
<div class="navbar-start">
<a class="navbar-item has-text-light" asp-area="" asp-page="/Index">Home</a>
<a class="navbar-item has-text-light" asp-area="" asp-page="/Privacy">Privacy</a>
</div>
<div class="navbar-end">
@if (SignInManager.IsSignedIn(User))
{
<div class="navbar-item">
Hello @User.Identity.Name!
</div>
<div class="navbar-item">
<form asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/", new { area = "" })" method="post">
<button type="submit" class="button is-primary is-outlined">Logout</button>
</form>
</div>
}
</div>
</div>
</nav>
</header>
<content class="container is-centered">
@RenderBody()
</content>
<footer class="footer">
<div class="container">
<div class="<content></content> has-text-centered">
© 2019 - WebApplication1 -
</div>
</div>
</footer>
<script src="~/js/vaidationH5.js" asp-append-version="true"></script> @* <== ここに追加 *@
@RenderSection("Scripts", required: false)
</body>
</html>
これで「RequiredH5」データアノテーションが完成です。
ログイン画面のバリデーション
ログイン画面のデータモデル(Areas/Identity/Account/Login.cshtml.cs)に作成した「RequiredH5」データバリデーションを設定します。
// (class InputModelの部分を抜粋)
public class InputModel
{
[RequiredH5(errorMessage: "メッセージ変更")]
public string UserID { get; set; }
[RequiredH5]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
これで実行すると、以下のようにエラーメッセージが表示されるようになります。
以上でバリデーションの対応は終わりです。(2019/10/15:「ASP.Net Core でjQuery無しでHTML5のバリデーションを利用する」にもう少し書き込んでいます。サンプルソースはGitHubに公開しています。)
これでようやくVue.jsについての作業に入ります。最初に記述したようにSPA(Single Page Application)にするつもりはなく、基本的にRazor Pageを利用したページを作成するのにVue.jsをクライアント側のコンポーネントの作成に利用したいのです。JQueryはよくできたライブラリだとは思いますが、動作の重さやセレクタの独自感が合わないんです。
この計画に挫折したらこの先の記述は無くなっちゃいますけど。