WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

.NET 6.0 ASP.NET Identity に MySQL 使用 (その2)

by WebSurfer 2023年2月12日 16:15

.NET 6.0 の ASP.NET MVC アプリで、プロバイダに Oracle 製 MySql.EntityFrameworkCore v6.0.10 を使って、ASP.NET Core Identity のユーザー情報のストアに MySQL を利用する話を書きます。

ASP.NET Identity に MySQL 使用

先の記事「.NET 6.0 ASP.NET Identity に MySQL 使用 (CORE)」では、その時点で Oracle 製 MySql.EntityFrameworkCore は v5.0.8 までしかリリースされていなかったので、プロバイダには Pomelo.EntityFrameworkCore.MySql を使用しています。

その後 Oracle 製 MySql.EntityFrameworkCore の .NET 6.0 用がリリースされたので試してみましたが、v6.0.7 までは Add-Migration に失敗して使えませんでした。

先日バージョンアップの状況を調べてみたら v6.0.10 がリリースされていたので試してみたら Add-Migration は成功するようになってました。その後 Update-Database でデータベースは生成され、Resister でのユーザー登録、Login でのユーザー認証も問題なくできます。(ただし、100% 完全に動くかどうかまでは未検証ですが)

と言うわけで、プロバイダに Oracle 製 MySql.EntityFrameworkCore v6.0.10 を使ってのアプリの作り方を、先の記事の Pomelo.EntityFrameworkCore.MySql と違う点だけ以下に書いておきます。

(1) NuGet パッケージ

NuGet で MySql.EntityFrameworkCore v6.0.10 をインストールします。他に必要なものとバージョンを合わせました。

MySql.EntityFrameworkCore

(2) Program.cs の修正

自動生成されれた Program.cs ファイルで、サービス登録のコードが SQL Server を使うように設定されているはずですが、これを MySQL を使うように変更します。以下のような感じです。

Program.cs の修正

(3) Add-Migration / Update-Database の実行

パッケージマネージャーコンソールから Add-Migration CreateIdentitySchema を実行します (CreateIdentitySchema という名前は任意です)。

xxxxx_CreateIdentitySchema.cs

Migrations と言う名前のフォルダとその中に xxxxx_CreateIdentitySchema.cs というファイル (xxxxx は作成時のタイムスタンプ) が生成されているはずですので確認してください。内容は Pomelo.EntityFrameworkCore.MySql を使った場合と若干異なります。

昔使った MySql.Data.EntityFrameworkCore(今は非推奨)で主キーの長さが指定されてなくて問題となったところは、Pomelo.EntityFrameworkCore.MySql と場合と同様に、varchar(255) に指定されています。

その後、Update-Database を実行すれば接続文字列で指定した名前で MySQL データベースが生成され、その中に ASP.NET Core Identity に必要なテーブルが一式生成されます。

MySQL データベース

Visual Studio から MVC アプリを起動し Register 画面からユーザー登録ができることを確認してください。登録したユーザーは上の MySQL データーベースに反映され、Login 画面から登録した ID とパスワードでログインできるようになります。

この記事の一番上の画像はログインした後で Manage 画面を開いたものです。

Tags: , , ,

CORE

img 要素の画像データを取得してアップロード

by WebSurfer 2023年2月1日 14:30

下の画像のように html の img 要素の src 属性に url が指定されてブラウザ上に表示されている状態で、その画像データを JavaScript で取得し、さらにサーバーにアップロードする方法を書きます。

img 要素の画像をアップロード

img 要素から直接画像データを JavaScript で取得する方法は無さそうです。少なくとも自分は見つけられませんでした。なので、img 要素の画像を一旦 canvas に描画し、canvas から画像データを取得するようにしました。

なお、この記事の方法は src 属性の url が同一オリジンになっている場合に限りますので注意してください。

クロスドメインになっている場合は、canvas から画像データを取得する toDataURL メソッドまたは toBlob メソッドで DOMException がスローされます。エラーメッセージは、例えば Chrome で toDataURL メソッドを使った場合、以下のようになります。

DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

以下にコード例を載せます。ASP.NET Core MVC の View のものですが、html とJavaScript のコードはそのまま使えるはずです。

コードのコメントに書いた「その1」は toDataURL メソッドを使って画像データを DataURL 形式の文字列として取得し JSON 文字列として送信するものです。データは BASE64 でエンコードされるのでバイナリ形式よりサイズが約 1.3 倍大きくなることに注意してください。

「その2」は toBlob メソッドを使って画像データをバイナリ形式で取得し multipart/form-data 形式で送信するものです。toBlob メソッドは非同期で動き、バイナリデータは引数のコールバックに渡されることに注意してください。

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

<h1>UploadImage</h1>

<input id="button1" type="button" value="Upload DataUrl" />
<input id="button2" type="button" value="Upload Blob" />
<br />
<img id="image1" src="/images/911GT2_2.jpg" alt="" />
<div id="result"></div>

@section Scripts {
    <script type="text/javascript">
        //<![CDATA[
        let myImage, uploadDataUrl, uploadBlob, myCanvas, resultDiv;

        // DOMContentLoaded イベントのリスナ設定
        window.addEventListener('DOMContentLoaded', () => {
            uploadDataUrl = document.getElementById("button1");
            uploadBlob = document.getElementById("button2");
            myImage = document.getElementById("image1");
            resultDiv = document.getElementById("result");
            myCanvas = document.createElement("canvas");

            uploadDataUrl.addEventListener('click', uploadDataUrlImage);
            uploadBlob.addEventListener('click', uploadBlobImage);
        });

        // その1
        // 上の img 要素の画像を canvas に描画、canvas の画像データ
        // を DataURL 形式の文字列として取得し JSON 文字列を組み立
        // て、それを fetch API を使ってサーバーに送信。
        const uploadDataUrlImage = async () => {
            const context = myCanvas.getContext('2d');
            myCanvas.setAttribute('width', myImage.width);
            myCanvas.setAttribute('height', myImage.height);
            context.drawImage(myImage, 0, 0);
            const dataUrl = myCanvas.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 = "アップロード失敗";
            }
        }

        // その2
        // img 要素の画像を canvas に描画、canvas の画像データを 
        // Blob 形式で取得し、それを fetch API を使ってサーバー
        // に送信。
        const uploadBlobImage = async () => {
            const context = myCanvas.getContext('2d');
            myCanvas.setAttribute('width', myImage.width);
            myCanvas.setAttribute('height', myImage.height);
            context.drawImage(myImage, 0, 0);

            const data = await getBlobFromCanvas();

            const formData = new FormData();
            formData.append("postedFile", data);
            const param = {
                method: "POST",
                body: formData
            }
            const response = await fetch("/api/Upload", param);
            if (response.ok) {
                const message = await response.text();
                resultDiv.innerText = message;
            } else {
                resultDiv.innerText = "アップロード失敗";
            }
        }

        // toBlob メソッドは非同期で動き、結果の Blob データは引数
        // のコールバックに渡されるので、結果は以下のように Promise
        // にラップして返す
        const getBlobFromCanvas = () => {
            return new Promise((resolve) => {
                myCanvas.toBlob((blob) => resolve(blob), "image/jpeg");
            });
        };

        //]]>
    </script>
}

上のコードで送信された画像データを受け取るサーバー側のコードは、「その1」の Data Url 形式のデータを JSON 文字列で受け取る場合は先の記事「canvas の画像をアップロード (その 2)」の下の方に例がありますので見てください。

「その2」のコードでは以下の Fiddler でのキャプチャ画像のようにバイナリデータが multipart/form-data 形式で送信されます。

Blob を maultipart/form-data 形式で送信

サーバー側のコードはそれを受け取って処理できるように書く必要があります。ASP.NET Core Web API でのコード例を下に載せておきます。

using Microsoft.AspNetCore.Mvc;

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

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

        // .NET 6.0 で作ったプロジェクトはnull許容参照型がデフォルトで有効に
        // 設定してあるので、引数を NULL 許容にしておかないと、クライアントでファ
        // イルを選択しないで送信した場合、HTTP 400 Bad Request エラーになって
        //  "The postedFile field is required."というエラーメッセージが返ってくる
        [HttpPost]
        public async Task<string> Post([FromForm] IFormFile? postedFile)
        {
            string result = "";
            if (postedFile != null && postedFile.Length > 0)
            {
                // アップロードされたファイル名を取得
                string filename = System.IO.Path.GetFileName(postedFile.FileName);

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

                string filePath = $"{contentRootPath}\\UploadedFiles\\" +
                    $"{filename}{DateTime.Now.ToString("yyyyMMddHHmmss")}.jpg";

                // フォルダ UploadedFile に画像ファイルを保存
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await postedFile.CopyToAsync(stream);
                }

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

            return result;
        }
    }
}

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

Tags: , , , ,

Upload Download

React 開発用サーバー証明書の更新

by WebSurfer 2023年1月28日 18:05

Visual Studio 2022 のテンプレートで作成する .NET 6.0 の React アプリは、Viusal Studio から実行すると React フロント側は Node.js 開発サーバーでホストされます。その Node.js 開発サーバーが HTTPS 通信で使用するサーバー証明書を更新する方法を書きます。

サーバー証明書エラー

この記事は React フロント側が Node.js 開発サーバーで実行される場合の話で、フロント側も IIS Express や Kestrel 上で実行される .NET Core 3.1 や .NET 5.0 のアプリでは関係ないので注意してください。

前置きが少々長くなりますが、まずどのような仕組みになっているかを書きます。

Visual Studio 2022 が ASP.NET Core アプリを実行して HTTPS 通信を行う際に使うサーバー証明書は、dotnet コマンドを使って発行するフレンドリ名が ASP.NET Core HTTPS development certificate という自己署名証明書を使います。

(注: ASP.NET Core アプリでも IIS Express でホストする場合は IIS Express Development Certificate が使用されます。下の画像の青枠の下のものです)

その有効期限は発効後 1 年で、それを過ぎると上の画像のようにサーバー証明書の期限切れエラーとなります。

Windows OS にインストールされている開発用サーバー証明書を更新することは dotnet コマンドを使うなどして可能です。下の画像の青枠の証明書が新たに発行して OS にインストールされた証明書です。

開発用サーバー証明書

ASP.NET Core アプリですとそれだけでサーバー証明書の期限れの問題は解決できるはずです。

しかしながら、.NET 6.0 の React アプリの場合はそれではエラーは解決しません。なぜかと言うと、Node.js 開発サーバーは独自に作成した証明書ファイルと秘密鍵ファイルを使っているからです。なので、それらが更新されない限りエラーは解決しません。

Visual Studio 2022 で作成する .NET 6.0 の React アプリで、HTTPS 通信のためにどのような設定が行われるかを以下に簡単に説明します。

まず、プロジェクトに含まれる Node.js のスクリプト aspnetcore-https.js が dotnet コマンドを使って自己署名証明書を発行し、証明書ファイルと秘密鍵ファイルを以下のフォルダに保存します。なお、ファイルが既に存在する場合はその操作はスキップされます。

%USERPROFILE%\AppData\Roaming\ASP.NET\https

ファイル名はデフォルトで証明書ファイルが <プロジェクト名>.pem、秘密鍵ファイルが <プロジェクト名>.key となります。

次に、aspnetcore-react.js がその証明書ファイルと秘密鍵ファイルを Node.js 開発サーバーがサーバー証明書として使うように設定します。

証明書ファイルと秘密鍵ファイルの場所の情報は、以下のように .env.development.local ファイルに含まれます。.env.development ファイルには PORT=44453 HTTPS=true という設定されていて、Node.js 開発サーバーが指定されたポートで HTTPS でリッスンするように指定されます。

.env.development.local

それらの設定により、Visual Studio 2022 から React アプリのプロジェクトを実行すると、Node.js 開発サーバーは .env.development.local に指定のパスにある証明書ファイルと秘密鍵ファイルを使用して HTTPS 通信を行うようになります。

そのあたりの仕組みはネットで見つけた記事「"React での ASP.NET Core" テンプレートで生成されるプロジェクトの仕組みを調べてみた」が参考になりました。詳しく知りたい方はその記事を読むことをお勧めします。

以上のようなわけで、Node.js 開発サーバーが使用する開発用サーバー証明書を更新するには、.env.development.local 指定のパスにある証明書ファイルと秘密鍵ファイルを新しいものに書き換えるということになります。

それには、ちょっとプリミティブなやり方ですが、既存の証明書ファイルと秘密鍵ファイルを削除するか名前を変えてから Visual Studio 2022 からプロジェクトを実行してやります。それにより、上に述べた Node.js のスクリプト aspnetcore-https.js が新しい証明書ファイルと秘密鍵ファイルを作成してくれます。

下の画像は、既存の reactnet6.pem と reactnet6.key をそれぞれ reactnet6_old.pem と reactnet6_old.key にリネームした後で (赤枠のファイル)、Visual Studio 2022 でプロジェクトを実行し、新たに reactnet6.pem と reactnet6.key を生成させた (青枠のファイル) 結果です。

証明書ファイルと秘密鍵ファイル

他にやり方は無いか探してみたのですが上記の方法以外見つかりませんでした。もっとスマートな方法がありそうな気がします。見つけたら追記します。

Tags: , , ,

React

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar