WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

DataGridView 新規追加行の未入力検証

by WebSurfer 9. October 2022 13:25

Windows Forms アプリの GataGridView の新規追加行に未入力のままフォーカスを移動したり[データの保存]ボタンをクリックした時にスローされる NoNullAllowedException に対処する方法を書きます。

NoNullAllowedException

NoNullAllowedException というのは、DataColumn.AllowDBNull プロパティが false に設定されている列に null 値を挿入しようとした場合にスローされる例外です。Visual Studio のデータソース構成ウィザードを使って型付 DataSet / DataTable を作ると、元の DB の列が NULL 不可になっている場合に false に設定されます。

DataGridView 上で当該列を空白のままにしてフォーカスを移動したり[データの保存]ボタンをクリックしたりすると DataTable の当該 Column に null 値を挿入しようとするので NoNullAllowedException がスローされます。

その際、エラーメッセージを表示するダイアログが、上の画像のものとは違って、下の画像のような「DataGridView の既定のエラーダイアログ」であれば、DataGridView.DataError イベントを処理することで対応できます。

DataGridView の既定のエラーダイアログ

具体例は Microsoft のドキュメント「DataGridView.DataError イベント」や「ユーザーがDataGridViewのセルに正しくない値を入力した時に発生するエラーを捕捉する」を見てください。下に載せたサンプルコードにも実装してあります。

しかしながら、DataError イベントをハンドルしても NoNullAllowedException を補足できないケースがあるのが問題です。

その補足できないケースというのは、自分が調べた限りですが、(1) BindingNavigator 上の[新規追加]ボタンを続けて 2 回クリック、(2) 新規追加行に未入力のまま BindingNavigator 上の[最初に移動][前に戻る][データの保存]ボタンをクリックした場合です。(他にもあるかも)

その場合は、この記事の一番上の画像のような、例のおなじみのエラーメッセージのダイアログが表示されます。

これを何とかしようとすると try - catch 句を追加して NoNullAllowedException を補足するということになると思います。

(他にもっとスマートな手段があるかもしれませんが分かりませんでした。DataGridView.RowValidating イベントをハンドルして対応できないかと考えたのですが、上に書いた「補足できないケース」ではイベントが発生しません)

どこに try - catch 句を追加するかですが、データソース構成ウィザードを使って型付 DataSet / DataTable を作り、それをデザイン画面で「データソース」ウィンドウから Form にドラッグ&ドロップして作ったアプリのコードにはその場所がありません。(Program.cs の Application.Run(new Form1()); で try - catch するのはやりすぎと思うので対象外)

そこで考えたのが、BindingNavigator の AddNewItem, DeleteItem, MoveFirstItem, MoveLastItem, MoveNextItem, MoveProviousItem プロパティの設定を以下のように (なし) に変更し(デフォルトでは当該 ToolStripButton が設定されている)、

BindingNavigator の設定変更

AddNewItem, DeleteItem, MoveFirstItem, MoveLastItem, MoveNextItem, MoveProviousItem に設定されていた各 ToolStripButton の Click イベントのハンドラを追加し、

Click イベントのハンドラ設定

その各ハンドラの中で BindingSource の AddNew, RemoveCurrent, MoveFirst, MoveLast, MoveNext, MoveProvious を実行するようにして try - catch 句を使うということです。

BindingNavigation の操作やマウスやキーボードを使っての BindingSource のポジションの変更に伴って BindingNavigator 上の ToolStripButton の Enabled プロパティの true/false の設定を変更する必要がありますが、それは BindingSource.PositionChanged イベントのハンドラで行います。

ただし、ここまでの設定だけでは[データの保存]ボタンをクリックした場合に対応できません。そこは、自動生成されたクリックイベントのハンドラの中の BindingSource.EndEdit(); というコードに try - catch を追加して NoNullAllowedException を補足して対処します。

以下にサンプルコードを書きます。例として、下の画像の SQL Server データベースを使います。

SQL Server データベース

まず、上の Product テーブルのレコード一覧表示・編集を行う Windows Forms + DataGridView アプリを作ります。それは Microsoft のドキュメント「新しいデータ ソースの追加」のように「データソース構成ウィザード」を使って型付 DataSet / DataTable を作り、それをデザイン画面で「データソース」ウィンドウから Form にドラッグ&ドロップすれば、自力ではコードを一行も書くことなく簡単に作成できます。

その後、上に述べたように、自動生成されたコードに手を加えてたのが下のサンプルコードです。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;

namespace WindowsFormsPkUnique
{
    public partial class Form3 : Form
    {
        public Form3()
        {
            InitializeComponent();

            // DataGridView.DataError イベントのハンドラ設定
            this.productDataGridView.DataError += 
                                  ProductDataGridView_DataError;

            // BindingSource.PositionChanged イベントのハンドラ設定
            this.productBindingSource.PositionChanged += 
                                  ProductBindingSource_PositionChanged;
        }

        private void Form3_Load(object sender, EventArgs e)
        {
            this.productTableAdapter.Fill(this.productDataSet.Product);
        }

        // DataGridView.DataError イベントを処理。これで補足できない
        // ケースがあるのが問題
        private void ProductDataGridView_DataError(object sender, 
            DataGridViewDataErrorEventArgs e)
        {
            if (e.Exception != null)
            {
                MessageBox.Show(this,
                    $"Index {e.RowIndex} の行でエラーが発生しました。\n\n" +
                    $"説明: {e.Exception.Message}",

                    "エラーが発生しました",
                    MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        // BindingSource の Position の変更に伴って、BindingNavigator の
        // 各ボタンの Enabled プロパティの true/false 設定を変更する
        private void ProductBindingSource_PositionChanged(object sender, 
            EventArgs e)
        {
            int count = productBindingSource.Count;
            int position = productBindingSource.Position;
            if (count <= 1)
            {
                bindingNavigatorMoveFirstItem.Enabled = false;
                bindingNavigatorMovePreviousItem.Enabled = false;
                bindingNavigatorMoveNextItem.Enabled = false;
                bindingNavigatorMoveLastItem.Enabled = false;
                if (count == 0)
                    bindingNavigatorDeleteItem.Enabled = false;
                else
                    bindingNavigatorDeleteItem.Enabled = true;
            }
            else
            {
                bindingNavigatorDeleteItem.Enabled = true;

                if (position == 0)
                {
                    bindingNavigatorMoveFirstItem.Enabled = false;
                    bindingNavigatorMovePreviousItem.Enabled = false;
                    bindingNavigatorMoveNextItem.Enabled = true;
                    bindingNavigatorMoveLastItem.Enabled = true;
                }
                else if (position == count - 1)
                {
                    bindingNavigatorMoveFirstItem.Enabled = true;
                    bindingNavigatorMovePreviousItem.Enabled = true;
                    bindingNavigatorMoveNextItem.Enabled = false;
                    bindingNavigatorMoveLastItem.Enabled = false;
                }
                else
                {
                    bindingNavigatorMoveFirstItem.Enabled = true;
                    bindingNavigatorMovePreviousItem.Enabled = true;
                    bindingNavigatorMoveNextItem.Enabled = true;
                    bindingNavigatorMoveLastItem.Enabled = true;
                }
            }
        }

        // BindingNavigator の[データの保存]ボタンクリックのハンドラ
        private void productBindingNavigatorSaveItem_Click(object sender, 
            EventArgs e)
        {
            this.Validate();            

            try
            {
                // EndEdit を try に含めないと NoNullAllowedException が
                // catch できない
                this.productBindingSource.EndEdit();

                this.tableAdapterManager.UpdateAll(this.productDataSet);
            }
            catch (NoNullAllowedException ex)
            {
                MessageBox.Show(this,
                    ex.Message + "\n\nデータを修正してください。",
                    "「データの保存」ボタン操作エラー",
                    MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
            catch (SqlException sqlEx)  // オマケの重複チェック
            {
                if (sqlEx.Number == 2601)
                {
                    MessageBox.Show(this,
                        $"Unique 制約違反です。\n\n説明: {sqlEx.Message}",
                        "エラーが発生しました",
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                else
                {
                    throw;
                }               
            }            
        }

        // BindingNavigator の[最初に移動]ボタンクリックのハンドラ
        private void bindingNavigatorMoveFirstItem_Click(object sender, 
            EventArgs e)
        {
            try
            {
                productBindingSource.MoveFirst();
            }
            catch (NoNullAllowedException ex)
            {
                MessageBox.Show(this,
                    ex.Message + "\n\nデータを修正してください。",
                    "「最初に移動」ボタン操作エラー",
                    MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        // BindingNavigator の「前に戻る」ボタンクリックのハンドラ
        private void bindingNavigatorMovePreviousItem_Click(object sender, 
            EventArgs e)
        {
            try
            {
                productBindingSource.MovePrevious();
            }
            catch (NoNullAllowedException ex)
            {
                MessageBox.Show(this,
                    ex.Message + "\n\nデータを修正してください。",
                    "「前に戻る」ボタン操作エラー",
                    MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        // BindingNavigator の「次に移動」ボタンクリックのハンドラ
        private void bindingNavigatorMoveNextItem_Click(object sender, 
            EventArgs e)
        {
            try
            {
                productBindingSource.MoveNext();
            }
            catch (NoNullAllowedException ex)
            {
                MessageBox.Show(this,
                    ex.Message + "\n\nデータを修正してください。",
                    "「次に移動」ボタン操作エラー",
                    MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        // BindingNavigator の「最後に移動」ボタンクリックのハンドラ
        private void bindingNavigatorMoveLastItem_Click(object sender, 
            EventArgs e)
        {
            try
            {
                productBindingSource.MoveLast();
            }
            catch (NoNullAllowedException ex)
            {
                MessageBox.Show(this,
                    ex.Message + "\n\nデータを修正してください。",
                    "「最後に移動」ボタン操作エラー",
                    MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        // BindingNavigator の[新規追加]ボタンクリックのハンドラ
        private void bindingNavigatorAddNewItem_Click(object sender, 
            EventArgs e)
        {
            try
            {
                productBindingSource.AddNew();
            }
            catch (NoNullAllowedException ex)
            {
                MessageBox.Show(this,
                    ex.Message + "\n\nデータを修正してください。",
                    "「新規追加」ボタン操作エラー",
                    MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        // BindingNavigator の[削除]ボタンクリックのハンドラ
        private void bindingNavigatorDeleteItem_Click(object sender, 
            EventArgs e)
        {
            productBindingSource.RemoveCurrent();
        }
    }
}

上のコードには一つオマケで重複入力の検証を追加しています。上の SQL Server のデータベースの画像にあるように ProductName には Unique 制約がかかっていますが、それも検証してエラーメッセージを MessageBox を使って出すようにしています。

SQL Server ですから複数のユーザーが同時にアクセスするのが前提です。なので、重複しているか否かは実際に INSERT する時でないと分かりません。それは、INSERT 操作を行ったとき SqlException がスローされ、エラーの種類を表す番号が 2601 であることで判断できます。

その具体的な例は上のコードの productBindingNavigatorSaveItem_Click メソッドを見てください。


以上ですが、厳しく検証したわけではありませんので、上のコードはあくまで参考程度にお願いします。ひょっとしたら検証漏れがあるかもしれません。

Tags: , , ,

.NET Framework

CheckBox と DropDownList の検証 (CORE)

by WebSurfer 7. March 2021 14:44

CheckBox には、例えばユーザーが条件を許諾したという意思表示のため、チェックを入れるのが必須というケースがあると思います。その検証をクライアント側とサーバー側の両方で行うためのカスタム検証属性を考えてみました。

CheckBox の検証

CheckBox の場合 RequiredAttribute ではクライアント側でもサーバー側でも検証はかかりません。RangeAttribute を使って [Range(typeof(bool), "true", "true")] というように設定するという手段がありますが、クライアント側での検証がかかりません。

というわけで、先の記事「ASP.NET Core MVC 検証属性の自作」とか「ファイルアップロード時の検証 (CORE)」で書いたようなカスタム検証属性を作って利用するのが良さそうです。

DropDownList にもそのようなカスタム検証属性を作ってみようと思いましたが、未選択で検証 NG とする場合は一番最初の option 要素が value="" となっていれば RequiredAttribute でクライアント/サーバー両方で検証がかかること、特定の項目を選ばなければならないというのは他の条件も絡むであろうから先の記事「CustomValidation 属性」で書いたようにすべきで、意味がなさそうなので止めました。

以下にそのコードをアップしておきます。上の画像を表示したものです。DropDownList 関係のコードもせっかく書いたので一緒に載せておきます。

Model とカスタム検証属性

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Globalization;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace MvcCoreApp.Models
{
    // ビューモデル
    public class Partner
    {
        [Required(ErrorMessage = "{0} is required.")]
        public string Name { get; set; }

        // Required 属性では検証はかからない
        [Required(ErrorMessage = "{0} is required.")]
        [CheckBoxValidate(true)]
        public bool PartnerAccepted { get; set; }

        [Required(ErrorMessage = "{0} is required.")]
        public string Area { get; set; }

        public SelectList AreaList { get; set; }
    }

    // DropDownList に渡す SelectList 生成用のクラス
    // AreaId は int? または string 型にしておけば、SelectList を
    // 生成する際、下の Controller のコード例のように null を設定
    // できる。結果 option value="" となり未選択で検証 NG とできる
    public class Area
    {
        public int? AreaId { set; get; }
        public string AreaName { set; get; }
    }

    // CheckBox 用の検証属性
    // true/false を引数で指定(要チェックなら true とする)
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class CheckBoxValidateAttribute : ValidationAttribute, 
                                             IClientModelValidator
    {
        private readonly bool option;

        public CheckBoxValidateAttribute(bool option)
        {
            this.option = option;
            this.ErrorMessage = "{0} must be set to {1}.";
        }

        public override string FormatErrorMessage(string name)
        {
            return String.Format(CultureInfo.CurrentCulture, 
                                 ErrorMessageString, 
                                 name, 
                                 option);
        }

        public override bool IsValid(object value)
        {
            // テキストボックスに入力された文字列が検証対象の場合は
            // ここで value が空白か否かをチェックして空白の場合は
            // true を返すようにしていた(空白の検証は Required だけで
            // 行い、その他の検証属性は空白の場合は true を返すのが
            // 原則なので)。チェックボックスの場合は ASP.NET MVC では
            // 必ず true/false のいずれかが返されるようになっている。
            // なので null が帰ってきた場合は何か良からぬことが起こって
            // いるかもしれないということで例外をスローすることにした
            if (value == null) 
            {
                throw new ArgumentNullException("CheckBoxValidate");
            }

            if ((bool)value == option)
            {
                return true;
            }
            return false;
        }

        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            var errorMessage = 
                FormatErrorMessage(context.ModelMetadata.GetDisplayName());
            MergeAttribute(context.Attributes, 
                           "data-val-checkboxvalidate", 
                           errorMessage);
            MergeAttribute(context.Attributes, 
                           "data-val-checkboxvalidate-option", 
                           this.option.ToString());
        }

        // 上の AddValidation メソッドで使うヘルパーメソッド
        private bool MergeAttribute(IDictionary<string, string> attributes, 
                                    string key, 
                                    string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
            attributes.Add(key, value);
            return true;
        }
    }
}

Controller / Action Method

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using MvcCoreApp.Models;
using Microsoft.AspNetCore.Http;
using System.IO;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace MvcCoreApp.Controllers
{
    public class ValidationController : Controller
    {
        public IActionResult CheckBoxDropDown()
        {
            // DropDwonList に渡す SelectList の生成
            // AreaId を null に設定すると結果 option value="" となり
            // 未選択では RequiredAttribute による検証は NG となる
            var list = new List<Area>
            { 
                new Area { AreaId = null, AreaName = "--- Select One ---"},
                new Area { AreaId = 1, AreaName = "North" },
                new Area { AreaId = 2, AreaName = "South"},
                new Area { AreaId = 3, AreaName = "Eastt" },
                new Area { AreaId = 4, AreaName = "West" }
            };

            var partner = new Partner 
            { 
                AreaList = new SelectList(list, "AreaId", "AreaName")
            };

            return View(partner);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult CheckBoxDropDown(Partner partner)
        {
            if (!ModelState.IsValid)
            {
                // 検証 NG で再表示する場合は SelectList も再生成要
                var list = new List<Area>
                {
                    new Area { AreaId = null, AreaName = "--- Select One ---"},
                    new Area { AreaId = 1, AreaName = "North" },
                    new Area { AreaId = 2, AreaName = "South"},
                    new Area { AreaId = 3, AreaName = "Eastt" },
                    new Area { AreaId = 4, AreaName = "West" }
                };

                partner.AreaList = new SelectList(list, "AreaId", "AreaName");

                return View(partner);
            }

            return RedirectToAction("Create", "Validation");
        }
    }
}

View

@model MvcCoreApp.Models.Partner

@{
    ViewData["Title"] = "CheckBoxDropDown";
}

<h1>CheckBoxDropDown</h1>

<h4>Pertner</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="CheckBoxDropDown">
            <div asp-validation-summary="ModelOnly" class="text-danger">
            </div>
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" />
                <span asp-validation-for="Name" class="text-danger"></span>
            </div>
            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="PartnerAccepted" />
                    @Html.DisplayNameFor(model => model.PartnerAccepted)
                </label>
                <br />
                <span asp-validation-for="PartnerAccepted" class="text-danger">
                </span>
            </div>
            <div class="form-group">
                <label asp-for="Area" class="control-label"></label>
                <select asp-for="Area" asp-items="Model.AreaList" 
                        class="form-control">
                </select>
                <span asp-validation-for="Area" class="text-danger"></span>
            </div>

            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}

<script type="text/javascript">
    //<![CDATA[
    $.validator.addMethod("checkboxvalidate",
        function (value, element, parameters) {
            // チェックされてないと value は undefined となり
            // value では判定できないので注意
            var result = element.checked;
            var isTrue = (parameters.toUpperCase() == 'TRUE');
            if (result == isTrue) {
                return true;
            }
            return false;
        });

    $.validator.unobtrusive.adapters.
        addSingleVal('checkboxvalidate', 'option');

    //]]>
</script>
}

Tags: , , , ,

Validation

ファイルアップロード時の検証 (CORE)

by WebSurfer 26. January 2021 22:32

ASP.NET Core. 3.1 MVC アプリでファイルをアップロードする際に、カスタム検証属性を利用してファイルのサイズとタイプをクライアント側とサーバー側の両方で検証し、検証結果 NG の場合はエラーメッセージを表示する方法について書きます。

ファイルアップロード時の検証

基本的には先の記事「ASP.NET Core MVC 検証属性の自作」の応用です。先の記事ではパラメータが 1 つだけでしたが、この記事ではファイルのサイズとタイプという 2 つを検証するところが大きな違いです。

クライアント側での検証は、Web Forms アプリの記事「FileUpload と CustomValidator」と同様に、ブラウザの HTML5 File API を利用して JavaScript でファイルのサイズとタイプ情報を取得し、外部から入力されるパラメータ値と比較して検証します。

そのパラメータ値はカスタム検証属性(下のコード例では FileValidationAttribute)をモデルのプロパティに設定する際に引数(下のコード例では 50000 と "image/jpeg")として渡すようにしています。

サーバー側では引数に渡されたパラメータ値(ファイルサイズ / タイプ)を容易に取得でき、検証属性クラスの IsValid メソッドに引数として渡される IFromFile オブジェクトから取得できるファイルサイズとタイプを比較して検証できます。問題はクライアント側での検証用 JavaScript / jQuery にそのパラメータ値をどのように渡すかです。

パラメータが 1 つだけなら先の記事「ASP.NET Core MVC 検証属性の自作」で書いた View の中のスクリプトのように addSingleVal メソッドを利用できます。しかし、2 つあると addSingleVal メソッドでは何ともならず、add メソッドを利用せざるを得ないようです。

add メソッドの使い方は、先の記事を書くときに調べた Unobtrusive Client Validation in ASP.NET MVC 3 という記事に記述がありましたが、具体的にどういうコードを書けばいいのかが分かりません。

やむを得ず検証用の控えめな JavaScript のソースコード jquery.validate.unobtrusive.js を調べると、addBool, addSingleVal, addMinMax メソッドは add メソッドをラップしたものだということが分かりました。その中の addMinMax メソッドが min, max というパラメータを 2 つ渡していますので、それを見よう見まねで書いてみたのが下の View の中のコードです。

Edge, Chrome のデバッガでスクリプトをステップ実行するなどして調べましたが一応は期待通り動いていることを確認しました。具体的にどのような仕組みで動いているかまでは分かっていませんが。(汗)

いかにサンプルコードをアップしておきます。上の画像を表示したものです。要点はコメントに書いたつもりですのでそれを見てください。(手抜きでスミマセン)

Model とカスタム検証属性

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Globalization;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace MvcCoreApp.Models
{
    // Model
    public class FileUpload
    {
        [Required(ErrorMessage = "{0} is required.")]
        public string Name { get; set; }

        [Display(Name = "Upload file")]
        [Required(ErrorMessage = "{0} is required.")]
        [FileValidation(50000, "image/jpeg")]
        public IFormFile PostedFile { get; set; }
    }

    // カスタム検証属性
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class FileValidationAttribute : ValidationAttribute, 
                                           IClientModelValidator
    {
        private readonly long size;
        private readonly string type;

        public FileValidationAttribute(long size, string type)
        {
            this.size = size;
            this.type = type;
            this.ErrorMessage = "{0} must be less than {1} byte, {2}";
        }

        public override string FormatErrorMessage(string displayName)
        {
            return String.Format(CultureInfo.CurrentCulture, 
                                 ErrorMessageString, displayName, 
                                 this.size, this.type);
        }

        public override bool IsValid(object value)
        {
            var postedFile = value as IFormFile;

            if (postedFile == null || postedFile.Length == 0)
            {
                return false;
            }

            if (postedFile.Length <= this.size && 
                postedFile.ContentType == this.type)
            {
                return true;
            }

            return false;
        }
        // IClientModelValidator が実装するメソッド。検証対象の input
        // 要素に控えめな JavaScript (jquery.validate.unobtrusive.js)
        // による検証のための属性と値を追加
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            var errorMessage = FormatErrorMessage(
                context.ModelMetadata.GetDisplayName());
            MergeAttribute(context.Attributes, 
                "data-val-filevalidation", errorMessage);
            MergeAttribute(context.Attributes, 
                "data-val-filevalidation-size", this.size.ToString());
            MergeAttribute(context.Attributes, 
                "data-val-filevalidation-type", this.type);
        }

        // 上の AddValidation メソッドで使うヘルパーメソッド
        private bool MergeAttribute(IDictionary<string, string> attributes, 
                                    string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
            attributes.Add(key, value);
            return true;
        }
    }
}

View

@model MvcCoreApp.Models.FileUpload

@{
    ViewData["Title"] = "Upload";
}

<h1>Upload</h1>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Upload" enctype="multipart/form-data">
            <div asp-validation-summary="ModelOnly" class="text-danger">
            </div>
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" />
                <span asp-validation-for="Name" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="PostedFile" class="control-label"></label>
                <input type="file" asp-for="PostedFile" />
                <br />
                <span asp-validation-for="PostedFile" class="text-danger">
                </span>
            </div>

            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}


<script type="text/javascript">
    //<![CDATA[
    $.validator.addMethod("filevalidation",
        function (value, element, parameters){
            // HTML5 File API の File, FileList がサポートされている
            // 場合のみ検証する。サポートされてないとスクリプトエラ
            // ーになるので下の if 文は必要
            if (window.File && window.FileList) {

                // ファイルが選択されてないと fileUpload.files[0] は
                // undefined になる。element.files[0] == null は念のため
                if (element.files[0] == undefined ||
                    element.files[0] == null) {
                    // ファイル未選択は RequiredAttribute で検証する
                    // こととし、ここでは true を返すことにした(検証
                    // とエラーメッセージがダブらないように)
                    return true;
                }

                if (element.files[0].type != parameters.type) {                    
                    return false;
                }

                if (element.files[0].size > Number(parameters.size)) {
                    return false;
                }

                return ture;
            } else {
                // HTML5 File API の File, FileList がサポートされて
                // いない場合はクライアント側では検証しない
                return ture;
            }
        });

    // パラメータを 2 つ渡す方法が問題。add メソッドを使わざるを得ない
    $.validator.unobtrusive.adapters.
        add("filevalidation", ["type", "size"], function (options) {

            // 上の検証スクリプトで parameters.type, parameters.size と
            // という形でパラメータを取得するには以下のように JavaScript
            // オブジェクトとして setValidationValues の第 3 引数に渡す。
            // 配列 [options.params.size,options.params.type] としても渡
            // せるが、取得するとき parameters[0] のようにする必要がある
            var value = {
                size: options.params.size,
                type: options.params.type
            };

            setValidationValues(options, "filevalidation", value);
        });

    // jquery.validate.unobtrusive.js からコピーしたヘルパーメソッド
    function setValidationValues(options, ruleName, value) {
        options.rules[ruleName] = value;
        if (options.message) {
            options.messages[ruleName] = options.message;
        }
    }
    //]]>
</script>
}

Comtroller / Action Method

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using MvcCoreApp.Models;
using Microsoft.AspNetCore.Http;
using System.IO;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace MvcCoreApp.Controllers
{
    public class ValidationController : Controller
    {
        public IActionResult Upload()
        {
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Upload(FileUpload model)
        {
            string result = "";
            IFormFile postedFile = model.PostedFile;

            if (ModelState.IsValid)            
            {
                string filename = Path.GetFileName(postedFile.FileName);
                
                result = filename + " (" + postedFile.ContentType + ") - " +
                        postedFile.Length + " bytes アップロード完了";
            }
            else
            {
                result = "ファイルアップロードに失敗しました";
            }

            ViewBag.Result = result;
            return View();
        }
    }
}

Tags: , , , ,

Upload Download

About this blog

2010年5月にこのブログを立ち上げました。その後ブログ2を追加し、ここはプログラミング関係、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  December 2022  >>
MoTuWeThFrSaSu
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar