2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ASP.NET Core でSPAではなくVue.jsを利用してみる(3)

Last updated at Posted at 2019-09-08

クライアント側に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」としています。

RequireH5Attribute.cs

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」を以下のように作成します。

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」の最後にスクリプトを追加しておきます。

_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">
                &copy; 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」データバリデーションを設定します。

Login.cshtml.cs

// (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; }
        }

これで実行すると、以下のようにエラーメッセージが表示されるようになります。

クライアントバリデーション01.png
クライアントバリデーション02.png

 以上でバリデーションの対応は終わりです。(2019/10/15:「ASP.Net Core でjQuery無しでHTML5のバリデーションを利用する」にもう少し書き込んでいます。サンプルソースはGitHubに公開しています。)

 これでようやくVue.jsについての作業に入ります。最初に記述したようにSPA(Single Page Application)にするつもりはなく、基本的にRazor Pageを利用したページを作成するのにVue.jsをクライアント側のコンポーネントの作成に利用したいのです。JQueryはよくできたライブラリだとは思いますが、動作の重さやセレクタの独自感が合わないんです。
 この計画に挫折したらこの先の記述は無くなっちゃいますけど。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?