WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

Model Binding Error Messages の差替 (CORE)

by WebSurfer 27. January 2023 12:35

先の記事「整数型プロパティの検証、エラーメッセージ (CORE)」の一番下に Model Binding Error Messages をローカライズする方法別途検証してみますと書きました。ローカライズと言うよりは、単に英語のエラーメッセージを日本語に差し替えるだけですが、やってみましたので以下にその方法を述べます。

Model Binding Error Messages 差替

.NET Framework の MVC5 アプリの場合は、先の記事「MVC5 でエラーメッセージが英語」に書いたように、NuGet から日本語のサテライトアセンブリ System.Web.Mvc.resources.dll をインストールするという手段がありますが、ASP.NET Core MVC ではそのような解決策は見つかりません。

自力でコードを書いてローカライズする方法は Stackoverflow の記事 ASP.NET Core Model Binding Error Messages Localization 他に見つかりましたのでそれを参考にしました。

だた、見つけた記事に書いてある方法は、多言語対応のため複数のリソースファイルを作り、それからカルチャに応じてその言語のエラーメッセージの文字列をリソースファイルから取得して、デフォルトのエラーメッセージを書き換えるという少々複雑なことを行っています。

先の記事のエラーメッセージ「The value '2000x' is not valid for 価格2 (int).」と「The value '' is invalid.」を日本語化するだけならコードはかなり簡単になります。リソースファイルを作ってそこからメッセージを取得するということは必要ありません。

それにはどうすればよいかと言うと、Program.cs (.NET 5.0 以前は Startup.cs) の AddControllersWithViews メソッドで DefaultModelBindingMessageProvider を取得し、SetAttemptedValueIsInvalidAccessor, SetValueMustNotBeNullAccessor メソッドを使ってデフォルトのエラーメッセージを差し替えてやります。

具体例は下のコードを見てください。Visual Studio 2022 のテンプレートで作った .NET 7.0 の ASP.NET Core MVC の場合のサンプルです。

builder.Services.AddControllersWithViews(options => { 
    DefaultModelBindingMessageProvider provider = 
        options.ModelBindingMessageProvider;
    provider.SetAttemptedValueIsInvalidAccessor(
        (s1, s2) => $"値 '{s1}' は {s2} に対して無効です。");
    provider.SetValueMustNotBeNullAccessor(
        (s) => $"値 '{s}' は無効です。");
});

上のコードにより、先の記事のデフォルトの英語のエラーメッセージ「The value '2000x' is not valid for 価格2 (int).」と「The value '' is invalid.」がそれぞれ「値 '2000x' は 価格2 (int) に対して無効です。」と「値 '' は無効です。」に書き換えられます。この記事の上の画像がその結果です。

デフォルトの Model Binding Error Messages には、上の AttemptedValueIsInvalid, ValueMustNotBeNull を含めて全部で 11 種類あります。

以下に一覧表を載せておきます。それぞれに SetXxxxxAccessor (Xxxxx は名前) というメソッドが用意されていて、それによりデフォルトのエラーメッセージを差し替えることができます。

名前 デフォルト 原因
AttemptedValueIsInvalid The value '{0}' is not valid for {1}. Exception is of type FormatException or OverflowException, value is known, and error is associated with a property.
ValueMustNotBeNull The value '{0}' is invalid. a null value is bound to a non-Nullable property.
MissingBindRequiredValue A value for the '{0}' parameter or property was not provided. a property with an associated BindRequiredAttribute is not bound.
MissingKeyOrValue A value is required. either the key or the value of a KeyValuePair<Key,TValue> is bound but not both.
MissingRequestBodyRequiredValue A non-empty request body is required. no value is provided for the request body, but a value is required.
NonPropertyAttemptedValueIsInvalid The value '{0}' is not valid. Exception is of type FormatException or OverflowException, value is known, and error is associated with a collection element or parameter.
NonPropertyUnknownValueIsInvalid The supplied value is invalid. Exception is of type FormatException or OverflowException, value is unknown, and error is associated with a collection element or parameter.
NonPropertyValueMustBeANumber The field must be a number. Error message HTML and tag helpers add for client-side validation of numeric formats. Visible in the browser if the field for a float (for example) collection element or action parameter does not have a correctly-formatted value.
UnknownValueIsInvalid The supplied value is invalid for {0}. Exception is of type FormatException or OverflowException, value is unknown, and error is associated with a property.
ValueIsInvalid The value '{0}' is invalid. Fallback error message HTML and tag helpers display when a property is invalid but the ModelErrors have nullErrorMessages.
ValueMustBeANumber The field {0} must be a number. Error message HTML and tag helpers add for client-side validation of numeric formats. Visible in the browser if the field for a float (for example) property does not have a correctly-formatted value.

Tags: , , ,

Validation

整数型プロパティの検証、エラーメッセージ (CORE)

by WebSurfer 26. January 2023 11:49

.NET 7.0 の ASP.NET Core MVC で、モデルのプロパティが整数型(int, short, long 等)の場合の検証とエラーメッセージに関する注意点を書きます。

検証結果

.NET Framework の MVC5 の場合については先の記事「int 型プロパティの検証、エラーメッセージ (MVC5)」に書きましたのでそちらを見てください。

検証に使ったのは Visual Studio 2022 v17.4.4 のテンプレートで作成した .NET 7.0 の ASP.NET Core MVC アプリです。View には Html ヘルパーではなく入力 Tag ヘルパーを使いました。

Model のプロパティが整数型の場合 (下に載せた Model のコード例では ContractID, Code プロパティ)、Required 属性を付与しなくても、View の入力 Tag ヘルパーから生成される html の input 要素には data-val-required="The xxx field is required." (xxx はプロパティ名) という属性が付与されます。

(プロパティが int? 型のような null 許容型の場合は data-val-required 属性は付与されません。.NET Framework MVC5 の場合に付与された data-val-number という属性は .NET 7.0 ASP.NET Core MVC では付与されません)

View で検証用の JavaScript ファイルが取り込まれるように設定されていると (下に載せた View のコード例では @section Scripts の中のコード)、未入力の場合はクライアント側での検証により data-val-required 属性に設定された "The xxx field is required." というメッセージが表示されます。

これを任意の文字列に書き換えるには、Model の当該プロパティに Required 属性を付与し ErrorMessage に書き換えたい文字列を設定します。そうすると、html の input 要素の data-val-required 属性に設定される文字列が ErrorMessage に置き換わり、検証 NG の場合はそれが表示されます。

未入力の検証については上記の対応だけで特に不都合はないと思います。

しかし、未入力の検証に加えて、数字か否かの検証を行う場合はいろいろ問題があります。それを以下に説明します。

入力 Tag ヘルパーが以下のようになっている場合、html の input 要素の type 属性が "number" となり、ブラウザによる入力の制約と、ASP.NET による検証がかかります。

<input asp-for="Price2" class="form-control"/>

入力の制約はブラウザ依存です。自分が試した限りですが、Edge 109.0.1518.61, Chrome 109.0.5414.120, Opera 94.0.4606.76 では数字と + - . 以外の文字は受け付けなくなります。Firefox 109.0 では何でも入力できてしまいます。

クライアント側での JavaScript による検証はフレームワークに組み込みの jquery.validate.min.js により行われます。数字以外の文字を入力して送信しようとすると、"Please enter a valid number." というエラーメッセージが表示され送信はキャンセルされます。("Please enter a valid number." というエラーメッセージは jquery.validate.min.js にハードコーディングされています。それを書き換えれば日本語化はできます)

問題は、(1) 文字 + - はブラウザは受け付けるが jquery.validate.min.js による検証で NG になるという不整合、(2) エラーメッセージが英語になること、(3) さらにそれを解決するためプロパティに RegularExpression 属性を追加しても無視されることです。

上記 (1) ~ (3) を解決するには、html の input 要素が type="number" ではなく type="text" になるようにします。具体的には、View の当該入力 Tag ヘルパーに以下のように type="text" を追加します。

<input asp-for="Price2" class="form-control" type="text"/>

それにより RegularExpression 属性に設定した正規表現が期待通り動くようになり、検証結果 NG の場合は ErrorMessage に設定したエラーメッセージが表示されるようになります。

以上をまとめると:

  1. 未入力の検証には、Model の当該プロパティに Required 属性を付与し ErrorMessage にエラーメッセージを設定。
  2. 数字か否かの検証には、当該入力 Tag ヘルパーに type="text" を追加して html の input 要素の type 属性が "text" となるようにし、さらに
  3. RegularExpression 属性をプロパティに付与して正規表現を使っての検証を行う。ErrorMessage にエラーメッセージを設定する。

 

以上はクライアント側での検証の話です。

サーバー側での検証の問題は、未入力または整数型にパースできない文字列を送信した場合、Model のプロパティに付与した Required 属性、RegularExpression 属性が働かないことです。上の画像の「価格2 (int)」のエラーメッセージを見てください。

上の画像の例では、クライアント側での検証を無効にして "2000x" という文字列を送信していますが「The value '2000x' is not valid for 価格2 (int).」というエラーメッセージが出ています。RegularExpression 属性に設定したエラーメッセージとは異なっています。

未入力の場合は「The value '' is invalid.」というエラーメッセージが表示されます。やはり Required 属性に設定したエラーメッセージとは異なります。

これは、"2000x" という文字列や空白は int 型にパースできないので、検証属性による検証が行われる前にエラーとなって、そのエラーメッセージが出ているようです。(想像です)

検証属性の ErrorMessage に設定したメッセージが表示されて欲しいのですが、int 型にパースできない文字列が送信された場合は何ともならないようです。ただし、このエラーメッセージを書き換える方法はあります。

ModelStateDictionary に含まれる ModelStateEntry は同じ Key でマージしすると上書きされます。具体例は下の Controller コードの通りです。上の画像の ID (int) のテキストボックスの下のエラーメッセージを書き換えています。

Model

using System.ComponentModel.DataAnnotations;

namespace MvcNet7App.Models
{
    public class Contract
    {
        [Display(Name = "ID (int)")]
        [Required(ErrorMessage = "{0} は必須")]
        [RegularExpression(@"^\d+$", ErrorMessage = "数字のみ")]
        public int ContractID { get; set; }

        [Display(Name = "契約日 (DateTime)")]
        [Required(ErrorMessage = "{0} は必須")]
        [RegularExpression(@"^\d{4}/\d{2}/\d{2}( \d{1,2}:\d{2}:\d{2})?$",
            ErrorMessage = "yyyy/MM/dd 形式")]
        public DateTime ContractDate { get; set; }

        [Display(Name = "価格 (decimal)")]
        [Required(ErrorMessage = "{0} は必須")]
        [RegularExpression(@"^\d{1,5}$", 
            ErrorMessage = "数字 1 ~ 5 文字")]
        public decimal Price { get; set; }

        [Display(Name = "価格2 (int)")]
        [Required(ErrorMessage = "{0} は必須")]
        [RegularExpression(@"^\d+$", ErrorMessage = "数字のみ")]
        [Range(100, 10000, 
            ErrorMessage = "{0}は{1}~{2}の間で入力してください。")]
        public int Price2 { get; set; }
    }
}

View

@model MvcNet7App.Models.Contract

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

<div class="row">
    <div class="col-md-4">
        <form asp-action="ContractEdit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="ContractID" class="control-label"></label>
                <input asp-for="ContractID" class="form-control"/>
                <span asp-validation-for="ContractID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ContractDate" class="control-label"></label>
                <input asp-for="ContractDate" class="form-control" type="text"/>
                <span asp-validation-for="ContractDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" type="text"/>
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price2" class="control-label"></label>
                <input asp-for="Price2" class="form-control" type="text"/>
                <span asp-validation-for="Price2" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{
        // コメントアウトするとクライアント側での検証がかからない
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
}

Controller

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using MvcNet7App.Models;
using System.Diagnostics;

namespace MvcNet7App.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public IActionResult ContractEdit()
        {
            var model = new Contract 
            { 
                ContractID = 1, 
                ContractDate = DateTime.Now, 
                Price = 1000m, 
                Price2 = 2000
            };

            return View(model);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult ContractEdit(Contract model)
        {
            if (ModelState.IsValid)
            {
                return RedirectToAction("Index");
            }

            // div asp-validation-summary に表示するために追加
            var newDictionary = new ModelStateDictionary();
            newDictionary.AddModelError("", 
                "ValidationSummary に表示するために追加。");
            ModelState.Merge(newDictionary);

            // エラーメッセージを書き換えることはできる。
            // 同じ Key でマージした方に上書きされる
            ModelStateEntry? state = ModelState["ContractID"];

            if (state != null && state.Errors.Count > 0)
            {
                string msg = state.Errors[0].ErrorMessage;
                if (msg.StartsWith("The value"))
                {
                    // マージすると RawValue が null になるので書き戻すた
                    // めに取得しておく
                    object? value = state.RawValue;

                    var newDictionary2 = new ModelStateDictionary();
                    newDictionary2.AddModelError("ContractID",
                        "書き換え。元は The value '1.0' is not valid for ID (int).");
                    ModelState.Merge(newDictionary2);

                    // RawValue を書き戻す。そうしないと再描画されたとき
                    // 元のユーザー入力が表示されず 0 になってしまう
                    state.RawValue = value;
                }
            }

            return View(model);
        }
    }
}

上に書いた「The value '2000x' is not valid for 価格2 (int).」とか「The value '' is invalid.」は Model Binding Error Messages というそうで、ローカライズする方法があるようです。上のように書き換えるよりローカライズする方が現実的かもしれません。

Stackoverflow の記事 ASP.NET Core Model Binding Error Messages Localization に方法が書いてあるのを見つけました。別途検証してみます。 ⇒ 2022/1/27 追記: 別の記事「Model Binding Error Messages の差替 (CORE)」に書きましたのでそちらを見てください。

Tags: , , , , ,

Validation

canvas の画像をアップロード (その 2)

by WebSurfer 20. November 2022 11:46

先の記事「canvas の画像をアップロード」の機能を .NET 6.0 の ASP.NET Core MVC アプリに実装しました。以下に備忘録として書いておきます。

canvas の画像をアップロード

基本的には先の記事と同じことを行っており、クライアントの PC にある画像を HTML5 の File API を利用して取得し、それを指定のサイズに縮小して HTML5 の canvas に描画し、canvas に描画された画像をサーバーにアップロードして保存するというものです。

先の記事との違いは、(1) アプリが先の記事では ASP.NET Web Forms であったものがこの記事では ASP.NET Core MVC であること、(2) jQuery は使わないようにしたこと(なので、jQuery ajax に代えて fetch API を使ってます)、(3) アップロードされてきたファイルを受けるのを ASP.NET Core Web API のコントローラーとしたことです。

概略の動きは以下の通りです。詳しくは下のサンプルコードとそれに書いたコメントを参照ください。

  1. クライアントによる画像ファイルの選択は HTML の <input type="file" ... /> を利用する。
  2. <input type="file" ... /> で画像ファイルが選択されたタイミングで、HTML5 File API の FileReader オブジェクトに readAsDataURL メソッド を使って選択された画像ファイルを読み込む。
  3. FileReader の result プロパティ を使って、読み込んだ画像ファイルを Data url 形式("data:image/jpeg;base64, ..." という文字列)で取得し、それを image オブジェクトの src 属性に設定する。
  4. その image オブジェクトを HTML5 の canvas に CanvasRenderingContext2D.drawImage() メソッド を使って描画する。その際、描画する画像の最大サイズの制限を設け(今回のサンプルでは 500 x 500 とした)、それに入る場合はそのまま、入らない場合は幅・高さどちらか大きい方を 500px に縮小し他方をその縮小率と同じに縮小(要するに縦横比を保ったまま 500 x 500 に入るよう縮小)する。
  5. canvas 上の縮小後の画像データを取得して Web サーバーに fetch API を使って送信するメソッドを作る。canvas からの画像データの取得は HTMLCanvasElement.toDataURL() メソッド を用いる。Data url 形式で取得できるので、それを fetch API を用いて JSON 形式で送信する。(BASE64 でエンコードされているので、バイナリ形式よりサイズが約 1.3 倍大きくなってしまうが・・・)
  6. <input type="button" ... /> タイプのボタンを配置し、その onclick 属性に上記のメソッドを設定する。
  7. 非同期でクライアントから送信された BASE64 形式の画像データは、ASP.NET Core Web API のコントローラーで受け、デコードしてバイナリ形式に戻してファイルに保存する。
  8. 先の記事と同様に、画像ファイルのサイズを 500,000 bytes に、ファイルのタイプを image/jpeg に制限する機能も実装した。

上記を実現するための JavaScript のコードを実装した View のサンプルコードは以下の通りです。

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

<h1>Canvas</h1>

<input type="file" id="file1" />
<input id="button1" type="button" value="Upload" style="display:none;" />
<br />
<div id="msg"></div>
<canvas id="mycanvas"></canvas>
<div id="result"></div>

@section Scripts {
    <script type="text/javascript">
    //<![CDATA[
        // 以下の操作を行っている:

        // File API の FileReader を利用して、input type="file" で
        // 選択された画像ファイルを読み込み、その画像の Data url 
        // 形式の文字列を取得。

        // その Data url 形式文字列を image オブジェクトの src 属
        // 性に設定するとロード完了時に image.onload イベントが発
        // 生するので、そのイベントのリスナで image オブジェクトの
        // 画像を指定のサイズ以下に縮小して canvas に描画する。

        // [Upload] ボタンクリックで uploadImage メソッドを起動。
        // そのメソッド内で canvas の画像データを DataURL 形式で取
        // 得して JSON 文字列を組み立て、それを fetch API を使って
        // サーバーに送信する。

        // 上に述べた FileReader と image オブジェクトへの参照。
        // 初期化は下の DOMContentLoaded イベントのリスナで行う
        let fileReader;
        let image;

        // 画像のサイズとタイプの制限。ここではそれぞれ 500000 
        // バイト、image/jpeg に制限している
        const maxFileSize = 500000;
        const allowedContentType = "image/jpeg";
 
        // canvas に描く画像のサイズ制限。ここでは 500 x 500 以下
        // に制限している
        const maxWidth = 500;
        const maxHeight = 500;

        // 上に定義されている input type="file" などのオブジェクト
        // をいちいち getElementById で取得しなくて済むように以下
        // の変数に保持。代入は下の DOMContentLoaded イベントのリ
        // スナで行う
        let inputFile, uploadButton, msgDiv, myCanvas, resultDiv;         

        // DOMContentLoaded イベントのリスナ設定
        window.addEventListener('DOMContentLoaded', () => {
            inputFile = document.getElementById("file1");
            uploadButton = document.getElementById("button1");
            msgDiv = document.getElementById("msg");
            myCanvas = document.getElementById("mycanvas");
            resultDiv = document.getElementById("result");

            // ブラウザの HTML5 File API サポートを確認
            if (window.File && window.FileReader && window.FileList) {

                // fileReader と image オブジェクトの初期化
                fileReader = new FileReader();
                image = new Image();                

                // [Upload] ボタンクリックで uploadImage メソッド
                // を起動できるようリスナとして設定
                uploadButton.addEventListener('click', uploadImage);

                // input type="file" でファイルの選択が完了すると 
                // change イベントが発生するのでそれにリスナをアタ
                // ッチし、以下の処置を行う
                inputFile.addEventListener('change', () => {
                    resultDiv.innerText = "";

                    // 選択されたファイルのタイプ/サイズの検証
                    // ClientValidate ヘルパメソッドは下の定義参照
                    if (ClientValidate(inputFile) == false) {
                        uploadButton.style.display = "none";
                        myCanvas.style.display = "none";
                        return;
                    }

                    // fileReader オブジェクトに input type="file" 
                    // で選択された画像ファイルを読み込む
                    fileReader.readAsDataURL(inputFile.files[0]);
                });

                // 上の readAsDataURL メソッドは非同期で動くので、
                // 読み込み完了の onloadend イベントのリスナで 
                // FileReader から Data url を取得し image オブジ
                // ェクトの src 属性に設定する
                fileReader.onloadend = () => {
                    image.src = fileReader.result; 
                };
                
                // image オブジェクトで画像のロードが完了すると 
                // image.onload イベントが発生するので、そのイベン
                // トのリスナで image オブジェクトが保持する画像を
                // サイズ制限以下に縮小して canvas 描画する。
                // DrawImageOnCanvas メソッドは下の定義参照
                image.onload = DrawImageOnCanvas;
            }
            else {
                inputFile.style.display = "none";
                myCanvas.style.display = "none";
                msgDiv.innerText =
                    "File API がサポートされていません。";
            }
        });

        // 選択されたファイルの検証のためのヘルパ関数
        function ClientValidate(fileUpload) {
            msgDiv.innerText = "";

            if (fileUpload.files[0] == null) {
                msgDiv.innerText = "ファイルが未選択です。";
                return false;
            }

            if (fileUpload.files[0].type != allowedContentType) {
                msgDiv.innerText = "選択されたファイルのタイプが " + 
                    allowedContentType + " ではありません。";
                return false;
            }

            if (fileUpload.files[0].size > maxFileSize) {
                msgDiv.innerText = "ファイルのサイズが " + 
                    maxFileSize + " バイトを超えています。";
                return false;
            }

            return true;
        }

        // 上で定義した image オブジェクトの src 属性に Data url
        // が設定され画像のロードが完了すると発生する onload イ
        // ベントにアタッチするリスナ。これで image 要素の画像を
        // 指定されたサイズ以下に縮小して canvas に描画する
        function DrawImageOnCanvas() {
            // オリジナル画像のサイズ
            const w = image.width;
            const h = image.height;

            let targetW, targetH;
            const context = myCanvas.getContext('2d');

            if (w <= maxWidth && h <= maxHeight) {
                // w, h ともに制限 maxWidth, maxHeight 以内 ⇒
                // そのままのサイズで canvas に描画
                myCanvas.setAttribute('width', w);
                myCanvas.setAttribute('height', h);
                context.drawImage(image, 0, 0);
            }
            else if (w < h) {
                // w, h どちらかが制限オーバーで h の方が大きい ⇒
                // 高さを maxHeight に縮小
                targetH = maxHeight;
                // 幅は高さの縮小比率で縮小
                targetW = Math.floor(w * targetH / h);
                myCanvas.setAttribute('width', targetW);
                myCanvas.setAttribute('height', targetH);
                context.drawImage(image, 0, 0, targetW, targetH);
            }
            else {
                // w, h どちらかが制限オーバーで w の方が大きい ⇒
                // 幅を maxWidth に縮小
                targetW = maxWidth;
                // 高さは幅の縮小比率で縮小
                targetH = Math.floor(h * targetW / w);
                myCanvas.setAttribute('width', targetW);
                myCanvas.setAttribute('height', targetH);
                context.drawImage(image, 0, 0, targetW, targetH);
            }

            uploadButton.style.display = "inline-block";
            myCanvas.style.display = "block";
        }

        // canvas の画像データを DataURL 形式で取得し、JSON 文
        // 字列を組み立てて fetch API で送信。
        async function uploadImage() {
            const context = myCanvas.getContext('2d');
            const dataUrl = context.canvas.toDataURL("image/jpeg");
            const params = {
                method: "POST",
                body: '{"imgBase64":"' + dataUrl + '"}',
                headers: { 'Content-Type': 'application/json' }
            }
            const response = await fetch("/api/Canvas", params);
            if (response.ok) { 
                const message = await response.text();
                resultDiv.innerText = message
            } else {
                resultDiv.innerText = "アップロード失敗";
            }
        }
    //]]>
    </script>
}

クライアントから JSON 形式で送信されてきた BASE64 形式の画像データは、以下のコードの ASP.NET Core Web API のコントローラーで受け、デコードしてバイナリ形式に戻してファイルに保存しています。

using Microsoft.AspNetCore.Mvc;

namespace MvcCore6App3.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class CanvasController : ControllerBase
    {
        // 物理パスの取得用
        private readonly IWebHostEnvironment _hostingEnvironment;

        public CanvasController(IWebHostEnvironment hostingEnvironment)
        {
            this._hostingEnvironment = hostingEnvironment;
        }

        [HttpPost]        
        public async Task<string> Post([FromBody] JsonData data)
        {
            // 文字列先頭の "data:image/jpeg;base64," を除去。
            string imgBase64 = 
                data.ImgBase64.Replace("data:image/jpeg;base64,", "");

            // BASE64 エンコードされた画像データを元のバイト列に変換
            Byte[] imgByteArray = Convert.FromBase64String(imgBase64);

            // ファイル名を設定
            string filename = 
                $"img{DateTime.Now.ToString("yyyyMMddHHmmss")}.jpg";

            // アプリケーションルートの物理パスを取得
            string contentRootPath = 
                _hostingEnvironment.ContentRootPath;

            string filePath = 
                $"{contentRootPath}\\UploadedFiles\\{filename}";

            // フォルダ UploadedFile に画像ファイルを保存
            await System.IO.File
                 .WriteAllBytesAsync(filePath, imgByteArray);

            return $"ファイル名 {filename} として保存しました。";
        }
    }

    // 先の記事の ASP.NET Web Forms アプリの Web メソッドでは
    // ReceiveImage(string imgBase64) という形で引数の imgBase64
    // に直接  {"imgBase64":"value"} の value を受け取ることがで
    // きたが、Web API ではそのようなことはできず、以下のような
    // クラスを定義して、それで受け取る必要がある
    public class JsonData
    {
        public string ImgBase64 { get; set; } = null!;
    }
}

JavaScript はブラウザ依存なところがありますが、Edge 107.0.1418.52, Chrome 107.0.5304.107, Firefox 107.0, Opera 93.0.4585.11 では期待通り動くことは確認しました。

Tags: , , , , ,

Upload Download

About this blog

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

Calendar

<<  February 2023  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
272812345
6789101112

View posts in large calendar