WebSurfer's Home

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

ASP.NET Identity で MySQL 利用 (CORE)

by WebSurfer 2020年5月14日 17:33

Core 3.1 ベース(.NET Framework ではありません)の ASP.NET MVC アプリで認証に ASP.NET Core Identity を用い、ユーザー情報のストアに MySQL を利用するにはどうするかということを書きます。

Visual Studio 2022 + .NET 6.0 の ASP.NET Core Identity の場合は「.NET 6.0 ASP.NET Identity に MySQL 使用 (CORE)」を見てください。

.NET Framework 版の ASP.NET Identity の場合は「ASP.NET Identity で MySQL 利用 (.NET 版)」を見てください。

ネットの情報ではサードパーティ製の Pomelo.EntityFrameworkCore.MySql を使ったという記事を目にしますが、ここでは NuGet でインストールできる Oracle 製の MySql.Data.EntityFrameworkCore v8.0.20 を使ってみました。

Oracle 製の MySql.Data.EntityFrameworkCore は Core に対応してないので下記ステップ「(6) プロジェクトのビルド」でエラーになったという話を聞きましたが、最新版ではそのあたりは対応されているようです。

ただし、何も問題がなかったわけではなく、最後に Update-Database でデータベース / テーブルを生成するところで "Specified key was too long; max key length is 3072 bytes" というエラーが出ます・・・が、解決は可能でした。詳しくは下のステップ「(8) Update-Database の実行」を見てください。

(1) プロジェクトの作成

Visual Studio 2019 のテンプレートを利用して ASP.NET Core 3.1 MVC アプリを作成します。認証は「認証なし」のままとしておきます。

新しい ASP.NET Core Web アプリケーションの作成

認証に「個別のユーザーアカウント」を選んでプロジェクト作成時点から ASP.NET Identity を実装しても、それを MySQL を利用するように変更できると思います。しかし、そうすると Login, Register 他の認証関係の機能は Razor Class Library (RCL) として提供され、ソースコードはプロジェクトには含まれません。

認証関係のソースコードを見たい / 修正したいことがあるでしょうから(少なくとも登録、ログインページは書き換えたくなるはず)、まず「認証なし」で作って下のステップ (2) に述べるようにスキャフォールディング機能を利用して ASP.NET Identity を実装するのがよさそうです。(「個別のユーザーアカウント」で作って、特定のページをオーバーライドすることもできるそうですが)

(2) ASP.NET Core Identity の実装

Microsoft のドキュメント Scaffold identity into an MVC project without existing authorization を参考に、スキャフォールディング機能を利用して ASP.NET Identity を実装します。

ドキュメントの通り、ソリューションエクスプローラーでプロジェクトを右クリック⇒[追加(D)]⇒[新しいスキャフォールディングアイテム(F)...]で下の画像のダイアログが開きます。

新規スキャフォールディングアイテムの追加

上のダイアログで[インストール済み]の項目から[ID]を選択し、[追加]をクリックすると以下の画像の「ID の追加」ダイアログが表示されます。

ID の追加

レイアウトページ欄にはステップ (1) で生成したプロジェクトのレイアウトページ ~/Views/Shared/_Layout.cshtml を設定します。

ソースコードが必要なファイルに[オーバーライドするファイルの選択]でチェックを入れます。(上の画像では[すべてのファイルをオーバーライド]にチェックを入れて全てのファイルを選択しています。どれを選んだらよいか分からなかったので)

[データコンテキストクラス(D)]には、+ をクリックすると表示されるデフォルトをそのまま設定しました。必要なら任意の名前に変更できます。デフォルトでは xxxxxContext の xxxxx がプロジェクト名になり、プロジェクトルート下に Areas/Identity/Data というフォルダが作られ、その中にコンテキストクラスを含むファイル xxxxxContext.cs が生成されます。

[ユーザークラス(U)]にも同様に、+ をクリックすると表示されるデフォルトをそのまま設定しました。これも任意の名前に変更できます。デフォルトでは IdentityUser を継承した xxxxxUser クラスが生成され、xxxxx がプロジェクト名になります。

[ユーザークラス(U)]に何も設定せず空白のままにしておくと IdentityUser がそのまま使われます。基本的な機能はそれで問題ないですが、もし、将来プロファイル情報を追加するようなことがあると困ることになりますので、設定しておいた方がよさそうです。

その他、NuGet での Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore の追加、Razor ページを有効にするための Startup.cs へのコードの追加を忘れないようにしてください。詳しくは上に紹介した Microsoft のドキュメントを見てください。

(3) MySql.Data.EntityFrameworkCore

NuGet で MySql.Data.EntityFrameworkCore をインストールします。この記事を書いた時点では v8.0.20 しか見つからなかったのでそれをインストールしました。

2021/11/26 追記: MySql.Data.EntityFrameworkCore はいつの間にか非推奨になってました。 代替えパッケージが MySql.EntityFrameworkCore とのことです。それを使って同様なことができるかは未検証・未確認です。

MySql.Data.EntityFrameworkCore

関連するパッケージの更新・インストールが自動的に行われます。その内容は以下の画像を見てください。

変更のプレビュー

(4) 接続文字列の変更

テンプレートで自動生成された接続文字列は appsetteins.json にありますが、それは LocalDB を利用するように設定されていますので MySQL に接続するように変更します。以下のような感じです。

接続文字列の変更

例えば、上の画像の database=coreidentity というようにデータベース名を指定すると、Entity Framework Code First の機能を使って coreidentity という名前のデータベースを新たに生成し、そこに必要なテーブルを生成してくれます。(データベース名は任意に設定できます。この記事で coreidentity としたのは単なる例です)

(5) IdentityHostingStartup.cs の修正

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

サービス登録の変更

なお、IdentityHostingStartup.cs ファイルは、上のステップ (2) のようにスキャフォールディング機能を利用して ASP.NET Identity を実装した場合に生成されるものです。プロジェクト作成の時点で認証を「個別のユーザーアカウント」とした場合は、サービス登録のコードは Startup.cs ファイルに含まれますので注意してください。

(6) プロジェクトのビルド

ここで一旦プロジェクトをビルドしてみました。以下の通り成功します。

プロジェクトのビルド

参考にさせていただいた記事「ASP.NET CoreでMySQLを利用する」ではここでエラーとなったそうです。

その記事では Core のバージョンは 2.0、MySql.Data.EntityFrameworkCore のバージョンは 8.0.6 だったそうです。この記事では Core は 3.1、MySql.Data.EntityFrameworkCore は 8.0.20 です。その差があるのでしょうか。

(7) Add-Migration の実行

パッケージマネージャーコンソールから Add-Migration CreateIdentitySchema を実行します(CreateIdentitySchema という名前は任意です。また、Core では Enable-Migration は不要になったそうです)。

Add-Migration を実行

Migrations フォルダには CreateIdentitySchema.cs ファイルが生成されます。ざっと見たところ内容は正しそうです。(ツールのバージョンが 3.1.2 でランタイムのバージョン 3.1.4 より古いという警告は出ていますが Migration には影響ないようです)

(8) Update-Database の実行

次に Update-Database を実行し、Entity Framework Code First の機能を利用して MySQL にデータベース / テーブルの生成を試みます・・・が、以下の画像の通りダメでした。

Update-Database 実行結果

メッセージを見ると AspNetUserRoles というテーブルを生成する際に "Specified key was too long; max key length is 3072 bytes" という制約のためエラーになったということのようです。

以下、少々話が長くなりますが、なぜそういうエラーになるかと、その解決方法を書きます。

データベース / テーブルは CreateIdentitySchema.cs ファイルのコードに基づいて生成されるのですが、主キーは全て、

UserId = table.Column<string>(nullable: false)

というというような maxLength が指定されない設定となります。その場合、上の画像にある通り、テーブルを生成する create 句では主キーは varchar(767) に設定されます。

varchar(767) の 767 という数字は MySQL の主キーに許容される最大バイト数で、それゆえ主キーに maxLength が指定されないと varchar(767) になるのだと思われます。

767 バイトという制限は、「InnoDBインデックスの最大キー長について」という記事によると、ある条件で 3,072 バイトまで拡張できるそうで、MySQL 5.7 以降がその条件に当てはまるそうです。この記事では MySQL 8.x を使っているのでエラーメッセージにある通り 3,072 バイトが上限になっているのだと思います。

何故 nvarchar ではなくて varchar になるのか不思議でしたが、MySQL のドキュメント 10.3.7 The National Character Set によると、"MySQL uses utf8 as this predefined character set" なのでどちらでも同じなのだそうです。ちなみに create 句で nvarchar と指定しても、生成されるのは varchar になるそうです。

この記事で使用している MySQL 8.0.19 で使用されている文字コードを確認してみましたが、以下のようになっていました。

使用されている文字コード

utf8mb4 というのは、10.1.10.7 utf8mb4 文字セット (4 バイトの UTF-8 Unicode エンコーディング) によると、"utf8 という名前の文字セットは、文字あたり最大 3 バイトを使用し、BMP 文字だけを含みます。utf8mb4 文字セットは、文字ごとに最大 4 バイトを使用し、補助文字をサポートします" ということだそうです。

utf8mb4 を使用していますので、主キーに設定された varchar(767) は 767 x 4 = 3,068 バイトになります。制限の 3,072 より小さいので単独の主キーなら varchar(767) で問題ないはずです。

しかし、ASP.NET Core Identity が使うテーブルには連結主キーが設定されるものがあり、それが最大 3,072 バイトの制約を超えるため "Specified key was too long; max key length is 3072 bytes" というエラーになったということのようです。

連結主キーが設定されるテーブルは AspNetUserRoles と AspNetUserTokens です。

それらのテーブルの主キーが varchar(767) とならないよう、CreateIdentitySchema.cs ファイルの当該コードに制限 maxLength: 128 を設定してやります。以下の画像の赤枠部分を見てください。

maxLength: 128 を設定

Id フィールドには d85de5ae-a926-45cd-831a-4835034f6b79 というように書式指定された Guid の文字列が設定されますので、varchar(128) で十分なはずです(少なくとも現時点では)。

上の画像のように maxLength: 128 を設定後 Update-Database を実行すれば MySQL に ASP.NET Core Identity に必要なデータベースとテーブルが生成されます。

その後で Visual Studio から MVC アプリを動かしてユーザー登録できます。登録したユーザーはデーターベースに反映されます。もちろん登録した ID とパスワードでアプリにログインできるようになります。

Tags: , , ,

CORE

スキャフォールディング機能 (CORE)

by WebSurfer 2020年3月16日 14:47

ASP.NET Core 3.1 MVC プロジェクトで、既存のデータベースからコンテキストクラスとテーブルクラスを生成し、それをベースに Visual Studio 2019 のスキャフォールディング機能を使って Create, Read, Update, Delete (CRUD) 操作を行うための Controller と View を自動生成する手順を書きます。

Create

先の記事「スキャフォールディング機能」で .NET Framework MVC5 プロジェクトのケースを紹介しましたが、今回の記事はそれと同等なことを ASP.NET Core 3.1 MVC プロジェクトで行うものです。

上の画像は作成した ASP.NET Core 3.1 MVC アプリの編集 (Create) 画面を Edge から呼び出して表示したものです。(理由不明ですが一部の項目のコードが生成されていません。Create 以外は OK)

先の MVC5 の記事と同様、この記事でも既存のデータベースとして Microsoft のサンプルデータベース Northwind を使用し、その中の Products テーブルの CRUD 操作を行うことにします。

大まかな手順は以下の通りです。

  1. SQL Server にサンプルデータベース Northwind をアタッチして接続できるようにしておく。
  2. Visual Studio Community 2019 のテンプレートを使用して ASP.NET Core 3.1 MVC プロジェクトを生成。
  3. リバースエンジニアリングによって Northwind データベースからコンテキストクラスとテーブルクラスを生成してプロジェクトに追加する。(EF Core では Visual Studio の ADO.NET Entity Data Model のウィザードで EDM を作るということはできません)
  4. 追加したコンテキストクラスとテーブルクラスをベースに、スキャフォールディング機能を使って CRUD 操作用の Controller と View を自動生成する。

以下に、Visual Studio で上の操作を行った際に表示された画像を貼って要点を書いておきます。

(1) リバースエンジニアリング

リバースエンジニアリング

Visual Studio のパッケージマネージャーコンソールで、Microsoft ドキュメント「リバースエンジニアリング」を参考に、既存の SQL Server データベース Northwind からコンテキストクラスとテーブルクラスを生成します。(各パラメータについては、こちらの記事 Scaffold-DbContext の方がまとまっていてわかりやすいかも)

パラメータの内 -Connection と -Provider の設定は必須です。

-Connection に設定する接続文字列ですが、文字列に空白がある場合(普通あるはず)は接続文字列全体をクォーテーション " で囲います。バックスラッシュ \ はエスケープする必要はありません。上の画像を見てください。

-Provider に設定するプロバイダ名は、SQL Server を使っている場合は Microsoft.EntityFrameworkCore.SqlServer とします。

その他のパラメータ設定はオプションです。上の画像の例では -ContextDir でコンテキストクラスを生成するディレクトリ、-OutputDir でテーブルクラスを生成するディレクトリ、-Tables で含めるテーブルの名前(今回は Products, Categories, Suppliers を指定)、-DataAnnotations で各プロパティにデータアノテーション属性を付与するように指定しています。

成功すると Build succeeded. と表示されます。

(2) 生成されたクラス

生成されたクラス

リバースエンジニアリングの結果、上のステップ (1) のパラメータで指定した通り DAL ディレクトリにコンテキストクラス NorthwindContext.cs、Models ディレクトリにテーブルクラス Products.cs, Categories.cs, Suppliers.cs が生成されています。

(3) スキャフォールディング

スキャフォールディング

Visual Studio のソリューションエクスプローラーで Controllers を右クリックして[追加(D)]⇒[コントローラー(T)...]⇒[新規スキャフォールディングアイテムの追加]⇒[Entity Framework を使用したビューがある MVC コントローラー]と進んで、出てきたダイアログでスキャフォールディングの設定を行います。

この記事では、モデルクラス(M) には CRUD 対象とする SQL Server のテーブルに対応する Products クラスを、データコンテキストクラス(D) には NorthwindContext.cs の NorthwindContext クラスを設定しています。

[追加]ボタンをクリックすると SQL Server の Products テーブルの CRUD に必要な Controller / Action Methods と View が一式自動生成されます。

(4) コードの変更・追加

この状態で実行すると "InvalidOperationException: Unable to resolve service for type 'NorthwindContext' while attempting to activate 'ProductsController'" というエラーになるはずです。

なので、Code First で作った時と同様な形になるよう、以下の変更・追加を行います。

  1. 接続文字列を appsettings.json に追加。JSON 形式で名前は "NorthwindConnection" とし、値はステップ (1) で設定したものと同じにします。なお、JSON 文字列なので \ はエスケープして \\ にする必要があることに注意してください。
  2. NorthwindContext.cs の NorthwindContext クラスに自動生成されているコードの中から、引数を取らないコンストラクタ NorthwindContext() をコメントアウト。
  3. ProductsController に NorthwindContext オブジェクトを DI できるよう、Startup.cs の ConfigureServices メソッドに services.AddDbContext<NorthwindContext>( ... ) を追加。

上記 1 ~ 3 の変更・追加の後アプリを実行すれば期待通り動きます。

但し、理由は不明ですが、スキャフォールディン機能で自動生成された Create.cshtml には項目 UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel のコードがありません。そこはだけは自力でコードを書く必要がありました。

上記ステップ (3) のスキャフォールディング前に、ステップ (4) のコードの変更・追加をしないとダメかもと思い、(3), (4) の順序を逆にして試してみましたが結果は同じでした。

その他の View、Index.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml には全項目のコードが生成されていました。

Tags: , , ,

CORE

ASP.NET Core MVC の Unicode Escape Sequence (UES)

by WebSurfer 2020年3月11日 14:40

ASP.NET Core 3.1 MVC の Controller.Json メソッドを使って .NET オブジェクトを JSON 文字列にシリアライズすると日本語の文字は Unicode Escape Sequence (以下 UES と書きます) という形にエスケープされます。

JSON 文字列

UES というのは \uxxxx という形で表される Unicode 文字で、xxxx はその文字の Unicode コードになります。以下に、UES になる理由と、UES ではなく UTF-8 で(即ち、エスケープしないで)JSON 文字列に出力する方法を書きます。

UES となる理由はシリアライザでエスケープ処理が行われているからのようで、日本語の文字に限らず非 ASCII 文字は全てデフォルトで UES になるそうです。そのことは Microsoft のドキュメント How to serialize and deserialize (marshal and unmarshal) JSON in .NET に書いてありました。(注: そのドキュメントに書いてありますが、ASCII 文字でも HTML-sensitive characters はエスケープされます。例えば、< とか > はそれぞれ \u003C および \u003E になります)

ちなみに ASP.NET Core 3.1 Web API では UES にはならず、日本語の文字も UTF-8 で出力されます。何故 MVC と Web API で違う結果になるのかは不明です。(たぶん、MVC では HtmlEncode、JavaScriptEncode、UrlEncode の 3 つのエンコーダーすべてでエスケープされるようになっている、Web API では以下のサンプルコードのように BMP の文字はエスケープ対象から外す設定になっているからではなかろうかと想像しています)

日本語の文字も UES ではなく UTF-8 で出力する、即ちエスケープされないように設定するにはどうするかを以下に書きます。

Controller.Json メソッドには第 2 引数に JsonSerializerOptions クラスを設定するオーバーロードがありますが、その JsonSerializerOptions.Encoder プロパティでエスケープ対象から外す文字の設定が可能なようです。

アクションメソッド単位で JsonSerializerOptions を設定する方法は Microsoft のドキュメント Configure System.Text.Json-based formatters を参照してください。具体的には以下のサンプルコードのように設定します。

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using WebAPI.Data;
using Microsoft.EntityFrameworkCore;

// エスケープ回避を設定するため追加
using System.Text.Json;
using System.Text.Encodings.Web;
using System.Text.Unicode;

namespace WebAPI.Controllers
{
  public class HomeController : Controller
  {
    private readonly BloggingContext _context;

    public HomeController(BloggingContext context)
    {
      _context = context;
    }

    public async Task<IActionResult> Json()
    {
      // Core は遅延ローディングが働かないので注意
      var list = await _context.Blogs.
                       Include(b => b.Posts).ToListAsync();

      // BMP 全てをエスケープしないよう設定
      // (WriteIndented はオマケ)
      return Json(list, new JsonSerializerOptions
      {
        Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
        WriteIndented = true,
      });
    }
  }
}

上記のようなアクションメソッド単位でなく、Startup.cs でプロジェクト全体に設定することもできます。それには、AddControllersWithViews メソッドに以下のように AddJsonOptions メソッドを適用してやります。

services.AddControllersWithViews().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.WriteIndented = true;
    options.JsonSerializerOptions.Encoder = 
        JavaScriptEncoder.Create(UnicodeRanges.All);
});

上のコードでは、JavaScriptEncoder.Create(UnicodeRange[]) メソッドの引数に UnicodeRanges クラスの All プロパティを渡して BMP(Basic Multilingual Plane・・・U+0000 から U+FFFF の範囲)の文字をエスケープ対象から外すように設定しています。(WriteIndented プロパティの設定は UES とは関係ありません。これにより JSON 文字列がインデントされ見やすくなるので追加しました)

結果は以下のようになります。

JSON 文字列

なお、UnicodeRanges クラスの説明では、"現時点では、UnicodeRange クラスでサポートされているのは、基本多言語面 (BMP) の名前付き範囲のみです" とのことですので注意してください。

上のサンプルコードのように BMP 全てをエスケープしないよう設定しても、例えば 𠀋 という文字 (u2000b) は BMP にありませんが、それを JSON にシリアライズすると、 \uD840\uDC0B (サロゲートペアの形) になります。Web API でも同じです。

さらに、BMP の範囲内の文字でも全角空白 (U+3000) はエスケープされます。理由は GitHub の記事 Can't serializ the '\u3000' when using with UnicodeRanges.All に書いてありますが、U+3000 のみならず Space_Separator [Zs] category に属する文字は U+0020 (半角空白) 以外はブロックされる仕様になっているからだそうです。ブロックする理由は "their potential to cause problems or errors in consuming applications." だそうです・・・が、JSON 文字列の一部に過ぎないのにエスケープすると何故 "problems or errors" が避けられるのか納得できませんけど。何か別の使い方を考慮しているのかもしれません。


【2023/12/23 追記】

<. >, & などの HTML-sensitive 文字や + など文字は、上のサンプルコードのように BMP 全てをエスケープしないよう設定しても、やはりエスケープされます。

それらの文字もエスケープされないようにするには JavaScriptEncoder.UnsafeRelaxedJsonEscaping を使います。

ただし、Microsoft のドキュメント「すべての文字のシリアル化」に書いてあるように、セキュリティの問題がありますので注意が必要です。

なお、上に書いた全角空白 (U+3000) は、JavaScriptEncoder.UnsafeRelaxedJsonEscaping を使っても、やはりエスケープされます。

Tags: , , , , ,

CORE

About this blog

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

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar