WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

.NET 6.0 で Windows Forms アプリ作成

by WebSurfer 4. December 2021 14:22

2021 年 11 月にリリースされた Visual Studio Community 2022 のテンプレートを使って .NET 6.0 版の Windows Forms アプリを作ってみました。Visual Studio のバージョンはこの記事を書いた時点で最新版の 17.0.2、SDK は 6.0.100、ランタイムは 6.0.0 です。

.NET 6.0 版の Windows Forms アプリ

まず一つ問題があって、プロジェクト作成直後はデザイン画面が表示されません。下の画像のようにソリューションエクスプローラーに表示されるアイコンは C# のクラスファイスのもので、ここからデザイナ画面を表示することもできません。また、Form1.resx ファイルも存在しません。

初期画面

Developer Community に VS 2022 - WinForms .NET Designer does not automatically show the design surface when creating a new project という報告が上がっていて年内には改善されるとのことです。その記事に workaround が紹介されていますが自分の環境ではダメでした。

自分が試した限りですが、自動生成された Form1.cs を削除し、ソリューションエクスプローラーを操作して「新しい項目の追加」ダイアログから[フォーム (Windows フォーム)]を選んで新たにフォームを追加すればデザイナ画面は表示されました。Form1.resx も生成されました。

ただし、それでだけで全て OK かどうかは分かりません。実際、他の問題として、v17.0.1 の時 Form1.resx に画像を含めると開かないということがありました (v17.0.2 で改善されたのかその問題は出なくなりましたが)。年内に fix すると言っているので、待てるのであればそれを待った方が良いかもしれません。

以下は、自動生成された Form1.cs を削除してから新たに追加するという方法でデザイナ画面の表示と Form1.resx の生成を行ってから進めた時の話です。

SQL Server データベースから Visual Studio 2022 のウィザードを利用して型付 DataSet / DataTable + TableAdapter を作り、テーブルのレコード一覧を DataGridView に表示し、ユーザーが DataGridView に表示されたデータを編集してからデーターベースに編集結果を反映するという Windows Forms 定番のアプリを作ります。

Microsoft のドキュメント「新しいデータ ソースの追加」には ".NET Core 開発ではサポートされていません" と書いてありますが、型付 DataSet / DataTable + TableAdapter を作ることは可能です。ただし、その記事に書いてあるように[データ ソース]ウィンドウからドラッグ&ドロップしてコードを自動生成させることはできませんので、そこは自力でコードを書くことになります。

まず、型付 DataSet / DataTable + TableAdapter を作ります。ソリューションエクスプローラーでプロジェクトを右クリック ⇒[追加(D)]⇒[新しい項目(W)...]で表示されるメニューの中に DataSet という項目がありますのでそれを使います。(注: 上に紹介した Microsoft のドキュメントのように[プロジェクト(P)]⇒[新しいデータソースの追加(N)...]と操作するとサポートされてないというメッセージが出てその先に進めません)

SQL Server サンプルデータベース Northwind の Products, Catagories, Suppliers テーブルから NorthwindDataSet.xsd という名前で DataSet を作ってみました。この記事で使うのは下の画像の中の Products + ProductsTableAdapter です。

型付 DataSet / DataTable + TableAdapter

ここまでは Windows Forms プロジェクトを作成したデフォルトの状態で進めることができます。しかし、ビルドしようとすると SqlConnection, SqlCommand, SqlDataAdapter, SqlTransaction 等が System.Data.SqlClient に見つからないというエラーになって失敗します。

SqlConnection, SqlCommand 等は .NET Core 3.1 でも .NET 5.0 でもデフォルトでは含まれておらず NuGet で System.Data.SqlClient をインストールする必要がありました。.NET 6.0 でも同じことのようです。

NuGet パッケージのインストール

上の画像の System.Data.Common の方は無くてもビルドは通りますが、SystemDBNull とか System.Data.DbType 等が含まれているとのことなので念のため追加しておきました。

この後、.NET Framework であれば作成した DataTable を[データ ソース]ウィンドウからフォームにドラッグ&ドロップするだけで完全な Windows Forms アプリが自動生成されますが、.NET Core 3.1 や .NET 6.0 ではここから先は自力でコードを書くことになります。

一番実装が面倒そうなのが BindingNavigator 周りだと思いますが、それをデザイン画面で実装しようとしても、そもそもツールボックスに BindingNavigator がありません。

どうも特定の OS に依存するアプリを .NET Core 3.1 や .NET 6.0 で作る場合のサポートは限られているということのようです。Windows OS に依存する Windows Forms アプリなら最初から .NET Framework ベースで作るのが正解のようです。

しかし、ツールボックスに BindingNavigator が無いからと言って .NET 6.0 アプリで使えないというわけではないようです。Visual Studio のデザイナが対応してないだけで、.NET Framework ベースで作ったアプリと同じコードを書けば同じように動くはずです。

ということで、既存の .NET Framework ベースで作ったアプリから BindingNavigator 他のコードを移植してみました。

まず、BindingNavigator が使う .png 画像を既存のアプリから取得して Form1.resx にコピーします。

BindingNavigator が使う .png 画像

.NET 5.0 以前では画像は base64 形式に変換されて .resx ファイル内に埋め込まれたのですが、.NET 6.0 ではプロジェクトルート直下に Resources という名前のフォルダが作られ、その中に画像ファイルが格納される点が異なります。

Resources フォルダの画像ファイル

その後、既存の .NET Framework アプリからコードを移植します。以下がその例で、この記事の一番上の画像がその実行結果です。

namespace WinFormsApp2
{
    public partial class Form1 : Form
    {
        private NorthwindDataSet northwindDataSet;
        private System.Windows.Forms.BindingSource productsBindingSource;
        private NorthwindDataSetTableAdapters.ProductsTableAdapter productsTableAdapter;
        private NorthwindDataSetTableAdapters.TableAdapterManager tableAdapterManager;
        private System.Windows.Forms.BindingNavigator productsBindingNavigator;
        private System.Windows.Forms.ToolStripButton bindingNavigatorAddNewItem;
        private System.Windows.Forms.ToolStripLabel bindingNavigatorCountItem;
        private System.Windows.Forms.ToolStripButton bindingNavigatorDeleteItem;
        private System.Windows.Forms.ToolStripButton bindingNavigatorMoveFirstItem;
        private System.Windows.Forms.ToolStripButton bindingNavigatorMovePreviousItem;
        private System.Windows.Forms.ToolStripSeparator bindingNavigatorSeparator;
        private System.Windows.Forms.ToolStripTextBox bindingNavigatorPositionItem;
        private System.Windows.Forms.ToolStripSeparator bindingNavigatorSeparator1;
        private System.Windows.Forms.ToolStripButton bindingNavigatorMoveNextItem;
        private System.Windows.Forms.ToolStripButton bindingNavigatorMoveLastItem;
        private System.Windows.Forms.ToolStripSeparator bindingNavigatorSeparator2;
        private System.Windows.Forms.ToolStripButton productsBindingNavigatorSaveItem;
        private System.Windows.Forms.DataGridView productsDataGridView;

        public Form1()
        {
            InitializeComponent();

            System.ComponentModel.ComponentResourceManager resources = 
                new System.ComponentModel.ComponentResourceManager(typeof(Form1));
            this.northwindDataSet = new NorthwindDataSet();
            this.productsBindingSource = new System.Windows.Forms.BindingSource(this.components);
            this.productsTableAdapter = new NorthwindDataSetTableAdapters.ProductsTableAdapter();
            this.tableAdapterManager = new NorthwindDataSetTableAdapters.TableAdapterManager();
            this.productsBindingNavigator = new System.Windows.Forms.BindingNavigator(this.components);
            this.bindingNavigatorAddNewItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorCountItem = new System.Windows.Forms.ToolStripLabel();
            this.bindingNavigatorDeleteItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorMoveFirstItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorMovePreviousItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorSeparator = new System.Windows.Forms.ToolStripSeparator();
            this.bindingNavigatorPositionItem = new System.Windows.Forms.ToolStripTextBox();
            this.bindingNavigatorSeparator1 = new System.Windows.Forms.ToolStripSeparator();
            this.bindingNavigatorMoveNextItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorMoveLastItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorSeparator2 = new System.Windows.Forms.ToolStripSeparator();
            this.productsBindingNavigatorSaveItem = new System.Windows.Forms.ToolStripButton();
            this.productsDataGridView = new System.Windows.Forms.DataGridView();
            ((System.ComponentModel.ISupportInitialize)(this.northwindDataSet)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.productsBindingSource)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.productsBindingNavigator)).BeginInit();
            this.productsBindingNavigator.SuspendLayout();
            ((System.ComponentModel.ISupportInitialize)(this.productsDataGridView)).BeginInit();
            this.SuspendLayout();

            // 
            // northwindDataSet
            // 
            this.northwindDataSet.DataSetName = "NorthwindDataSet";
            this.northwindDataSet.SchemaSerializationMode = 
                System.Data.SchemaSerializationMode.IncludeSchema;
            // 
            // productsBindingSource
            // 
            this.productsBindingSource.DataMember = "Products";
            this.productsBindingSource.DataSource = this.northwindDataSet;
            // 
            // productsTableAdapter
            // 
            this.productsTableAdapter.ClearBeforeFill = true;
            // 
            // tableAdapterManager
            // 
            this.tableAdapterManager.BackupDataSetBeforeUpdate = false;
            this.tableAdapterManager.ProductsTableAdapter = this.productsTableAdapter;
            this.tableAdapterManager.UpdateOrder = 
                NorthwindDataSetTableAdapters.TableAdapterManager.UpdateOrderOption.InsertUpdateDelete;
            // 
            // productsBindingNavigator
            // 
            this.productsBindingNavigator.AddNewItem = this.bindingNavigatorAddNewItem;
            this.productsBindingNavigator.BindingSource = this.productsBindingSource;
            this.productsBindingNavigator.CountItem = this.bindingNavigatorCountItem;
            this.productsBindingNavigator.DeleteItem = this.bindingNavigatorDeleteItem;
            this.productsBindingNavigator.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.bindingNavigatorMoveFirstItem,
            this.bindingNavigatorMovePreviousItem,
            this.bindingNavigatorSeparator,
            this.bindingNavigatorPositionItem,
            this.bindingNavigatorCountItem,
            this.bindingNavigatorSeparator1,
            this.bindingNavigatorMoveNextItem,
            this.bindingNavigatorMoveLastItem,
            this.bindingNavigatorSeparator2,
            this.bindingNavigatorAddNewItem,
            this.bindingNavigatorDeleteItem,
            this.productsBindingNavigatorSaveItem});
            this.productsBindingNavigator.Location = new System.Drawing.Point(0, 0);
            this.productsBindingNavigator.MoveFirstItem = this.bindingNavigatorMoveFirstItem;
            this.productsBindingNavigator.MoveLastItem = this.bindingNavigatorMoveLastItem;
            this.productsBindingNavigator.MoveNextItem = this.bindingNavigatorMoveNextItem;
            this.productsBindingNavigator.MovePreviousItem = this.bindingNavigatorMovePreviousItem;
            this.productsBindingNavigator.Name = "productsBindingNavigator";
            this.productsBindingNavigator.PositionItem = this.bindingNavigatorPositionItem;
            this.productsBindingNavigator.Size = new System.Drawing.Size(1136, 25);
            this.productsBindingNavigator.TabIndex = 0;
            this.productsBindingNavigator.Text = "bindingNavigator1";
            // 
            // bindingNavigatorAddNewItem
            //
#nullable disable
            this.bindingNavigatorAddNewItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorAddNewItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorAddNewItem.Image")));
            this.bindingNavigatorAddNewItem.Name = "bindingNavigatorAddNewItem";
            this.bindingNavigatorAddNewItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorAddNewItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorAddNewItem.Text = "新規追加";
            // 
            // bindingNavigatorCountItem
            // 
            this.bindingNavigatorCountItem.Name = "bindingNavigatorCountItem";
            this.bindingNavigatorCountItem.Size = new System.Drawing.Size(29, 22);
            this.bindingNavigatorCountItem.Text = "/ {0}";
            this.bindingNavigatorCountItem.ToolTipText = "項目の総数";
            // 
            // bindingNavigatorDeleteItem
            // 
            this.bindingNavigatorDeleteItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorDeleteItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorDeleteItem.Image")));
            this.bindingNavigatorDeleteItem.Name = "bindingNavigatorDeleteItem";
            this.bindingNavigatorDeleteItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorDeleteItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorDeleteItem.Text = "削除";
            // 
            // bindingNavigatorMoveFirstItem
            // 
            this.bindingNavigatorMoveFirstItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorMoveFirstItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorMoveFirstItem.Image")));
            this.bindingNavigatorMoveFirstItem.Name = "bindingNavigatorMoveFirstItem";
            this.bindingNavigatorMoveFirstItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorMoveFirstItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorMoveFirstItem.Text = "最初に移動";
            // 
            // bindingNavigatorMovePreviousItem
            // 
            this.bindingNavigatorMovePreviousItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorMovePreviousItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorMovePreviousItem.Image")));
            this.bindingNavigatorMovePreviousItem.Name = "bindingNavigatorMovePreviousItem";
            this.bindingNavigatorMovePreviousItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorMovePreviousItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorMovePreviousItem.Text = "前に戻る";
            // 
            // bindingNavigatorSeparator
            // 
            this.bindingNavigatorSeparator.Name = "bindingNavigatorSeparator";
            this.bindingNavigatorSeparator.Size = new System.Drawing.Size(6, 25);
            // 
            // bindingNavigatorPositionItem
            // 
            this.bindingNavigatorPositionItem.AccessibleName = "位置";
            this.bindingNavigatorPositionItem.AutoSize = false;
            this.bindingNavigatorPositionItem.Font = new System.Drawing.Font("Yu Gothic UI", 9F);
            this.bindingNavigatorPositionItem.Name = "bindingNavigatorPositionItem";
            this.bindingNavigatorPositionItem.Size = new System.Drawing.Size(50, 23);
            this.bindingNavigatorPositionItem.Text = "0";
            this.bindingNavigatorPositionItem.ToolTipText = "現在の場所";
            // 
            // bindingNavigatorSeparator1
            // 
            this.bindingNavigatorSeparator1.Name = "bindingNavigatorSeparator1";
            this.bindingNavigatorSeparator1.Size = new System.Drawing.Size(6, 25);
            // 
            // bindingNavigatorMoveNextItem
            // 
            this.bindingNavigatorMoveNextItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorMoveNextItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorMoveNextItem.Image")));
            this.bindingNavigatorMoveNextItem.Name = "bindingNavigatorMoveNextItem";
            this.bindingNavigatorMoveNextItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorMoveNextItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorMoveNextItem.Text = "次に移動";
            // 
            // bindingNavigatorMoveLastItem
            // 
            this.bindingNavigatorMoveLastItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorMoveLastItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorMoveLastItem.Image")));
            this.bindingNavigatorMoveLastItem.Name = "bindingNavigatorMoveLastItem";
            this.bindingNavigatorMoveLastItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorMoveLastItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorMoveLastItem.Text = "最後に移動";
            // 
            // bindingNavigatorSeparator2
            // 
            this.bindingNavigatorSeparator2.Name = "bindingNavigatorSeparator2";
            this.bindingNavigatorSeparator2.Size = new System.Drawing.Size(6, 25);
            // 
            // productsBindingNavigatorSaveItem
            // 
            this.productsBindingNavigatorSaveItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.productsBindingNavigatorSaveItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("productsBindingNavigatorSaveItem.Image")));
            this.productsBindingNavigatorSaveItem.Name = "productsBindingNavigatorSaveItem";
            this.productsBindingNavigatorSaveItem.Size = new System.Drawing.Size(23, 22);
            this.productsBindingNavigatorSaveItem.Text = "データの保存";
            this.productsBindingNavigatorSaveItem.Click += ProductsBindingNavigatorSaveItem_Click;
#nullable enable
            // 
            // productsDataGridView
            // 
            //this.productsDataGridView.AutoGenerateColumns = false;
            this.productsDataGridView.ColumnHeadersHeightSizeMode = 
                System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.productsDataGridView.DataSource = this.productsBindingSource;
            this.productsDataGridView.Dock = System.Windows.Forms.DockStyle.Fill;
            this.productsDataGridView.Location = new System.Drawing.Point(0, 25);
            this.productsDataGridView.Name = "productsDataGridView";
            this.productsDataGridView.RowTemplate.Height = 21;
            //this.productsDataGridView.Size = new System.Drawing.Size(1136, 643);
            this.productsDataGridView.TabIndex = 1;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
            this.Controls.Add(this.productsDataGridView);
            this.Controls.Add(this.productsBindingNavigator);
            this.Name = "Form1";
            this.Text = "Northwind";
            this.Load += Form1_Load;
            ((System.ComponentModel.ISupportInitialize)(this.northwindDataSet)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.productsBindingSource)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.productsBindingNavigator)).EndInit();
            this.productsBindingNavigator.ResumeLayout(false);
            this.productsBindingNavigator.PerformLayout();
            ((System.ComponentModel.ISupportInitialize)(this.productsDataGridView)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        private void Form1_Load(object? sender, EventArgs e)
        {
            this.productsTableAdapter.Fill(this.northwindDataSet.Products);
        }

        private void ProductsBindingNavigatorSaveItem_Click(object? sender, EventArgs e)
        {
            this.Validate();
            this.productsBindingSource.EndEdit();
            this.tableAdapterManager.UpdateAll(this.northwindDataSet);
        }
    }
}

既存の .NET Framework アプリのコードと違うところは、DataGridView の AutoGenerateColumns プロパティをデフォルトの true として各列を DataTable から自動生成させていることろです。ひょっとしたらそれによる予期せぬ副作用があるかもしれません。

もう一つ、接続文字列が TableAdapter のコードの中にハードコーディングされているところが .NET Framework アプリと違います。.NET Framework アプリの場合、接続文字列は設定ファイルに格納され、TableAdapter はそれから読んでくるのですが、.NET 6.0 版はそこのところにも手を加える必要があります。

こんな手間をかけるなら、最初から .NET Framework 版で作ってコードはすべて Visual Studio のデザイナで自動生成されればいいと言われそうですが、確かにその通りだと思いました。

Tags: , ,

CORE

Windows Forms 用 Chart Samples

by WebSurfer 30. November 2021 14:28

Chart Samples は Microsoft のサイトから Windows Forms 用と ASP.NET Web Forms 用の両方を入手できたのですが、そのページは現在リンク切れとなっており復活しそうもありません。Windows Forms アプリ用だけは別の入手先を見つけましたので、そ��を以下に書いておきます。

Windows Forms 用 Chart Samples

Windows Forms 用 Chart Samples は 2021/11/30 現在以下の GitHub のページからダウンロードできます。

Microsoft Chart for Windows Forms Samples Environment

上のページの右の方にある Latest というリンクをクリックするとダウンロードページに遷移しますので、そこで[Source code (zip)]をクリックすれば Samples-Environments-for-Microsoft-Chart-Controls-1.0.1.zip という名前の .zip ファイルがダウンロードされます。

ダウンロードした .zip ファイル

その zip ファイルの中に src というフォルダがあってそれに完全な Windows Forms アプリのソリューションとしてサンプルが含まれています。

それを適当なフォルダに解凍し、Visual Studio で WinFormsChartSamples.sln を開いて実行すれば、基本の解説、いろいろなタイプのサンプルのデモ、それを作るための C# および VB.NET サンプルコード等が満載の Windows Forms アプリが動くはずです。それがこの記事の一番上の画像です。

GitHub のページには "This is a backup of the Microsoft Chart for Windows Forms Samples Environment (Microsoft Chart Controls for Microsoft .NET Framework 3.5) ..." という記述がありますが、Visual Studio 2022 でソリューションを開いて[対象のフレームワーク(G)]みると .NET Framework 4 となってました。(3.5 というのは間違い?)

なお、ビルドする際、プロジェクトルート直下の MainForm.resx と Utilities\SampleMain フォルダの MainForm.resx に対して以下のエラーが出るかもしれません。

"ファイル MainForm.resx を処理できませんでした。インターネットまたは制限付きゾーン内にあるか、ファイルに Web のマークがあるためです。これらのファイルを処理するには、Web のマークを削除してください"

その場合は、エクスプローラーで対象のファイルのプロパティを開き、下の画像の赤枠で示したセキュリティの部分の[許可する(K)]にチェックを入れて[OK]をクリックすれば解決するはずです。

MainForm.resx のプロパティ

Microsoft のサイトからサンプルを入手した場合の設定方法はなどは先の記事は「Chart Samples」に書きました。今となっては役に立たなかもしれませんが、復活するかもしれないので記事は残しておきます。

Tags: , ,

.NET Framework

.NET 6.0 ASP.NET Identity に MySQL 使用 (CORE)

by WebSurfer 27. November 2021 13:53

先の記事「ASP.NET Identity で MySQL 利用 (CORE)」に、ASP.NET Core 3.1 MVC アプリで ASP.NET Identity のユーザー情報のストアに MySQL を利用するにはどうするかということを書きましたが、その Visual Studio 2022 + .NET 6.0 版です。

先の記事と違うのは、Visual Studio Community 2022 のテンプレートを使って .NET 6.0 でプロジェクトを作ったところと、非推奨になった NuGet パッケージ MySql.Data.EntityFrameworkCore に代えて Pomelo.EntityFrameworkCore.MySql を使ったところです。

先の記事で使った MySql.Data.EntityFrameworkCore は非推奨になったので、まずその代替えの Oracle 製 MySql.EntityFrameworkCore を使おうと思いましたが、この記事を書いた時点での最新バージョンが 5.0.8 で .NET 6.0 には対応してなさそうです。6.0.0-preview3.1 というのがありましたがプレビュー版ですし、依存関係が net5.0 と書いてあったので使うのは止めました。(一応 5.0.8 を試してみましたが、Add-Migration に失敗します)

Oracle は Entity Framework 対応は積極的ではなさそうな感じで、MySql.EntityFrameworkCore のバージョン 6.0.0 の正式リリースはいつになるか分からないようです。一方、Pomelo.EntityFrameworkCore.MySql はバージョン 6.0.0 がすでにリリースされていましたので、この記事ではそれを使ってみました。

結果、Pomelo.EntityFrameworkCore.MySql バージョン 6.0.0 で一切支障なく ASP.NET Identity 用の MySQL データベースを構築できました。(Oracle にはあまり期待しない方が良いのかも)

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

Visual Studio 2022 のテンプレートを利用して .NET 6.0 の ASP.NET MVC アプリを作成します。「追加情報」の設定で[認証の種類(a)]は必ず「なし」としてください。

新しいプロジェクトの作成

(2) ASP.NET Core Identity の実装

ASP.NET Core Identity はスキャフォールディング機能を使って実装しますが、その前に、NuGet で Microsoft.VisualStudio.Web.CodeGeneration.Design をインストールします。

Web.CodeGeneration.Design

その後で Microsoft のドキュメント「ASP.NET Core プロジェクトでの Identity のスキャフォールディング」を参考に ASP.NET Core Identity を実装します。

ID の追加

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

ASP.NET Core Identity 関係のすべてのファイルを取り込むため[すべてのファイルをオーバーライド]にチェックを入れます。

コンテキストクラス名、エンティティクラス名は任意ですが、この記事では ApplicationDbContext, ApplicationUser としました。 何故かデザイナの + ボタンをクリックすると現れるダイアログのテキストボックス内で名前を設定しないとダメなので注意してください。また、エンティティクラス名を設定する際、コンテキストクラス名と同じ名前空間を追加しないと、コンテキストクラスと異なる名前空間になり、後で面倒なことになるので注意してください。

その後、追加した ASP.NET Core Identity 関係の Razor ページが働くよう、以下の追加・修正を行います。Visual Studio 2022 + .NET 6.0 で作ったプロジェクトでは Stratup.cs は無くなっていますので注意してください。サービス、ミドルウェア追加のためのコードは Program.cs に移すことにしたらしいです。

  1. Program.cs に builder.Services.AddRazorPages(); を追記。
  2. Program.cs の app.MapControllerRoute(name: "default", ... ); を書き換えて endpoints.MapRazorPages(); を追加。(これが無いと Razor ページのルーティングが働かないので必須)
  3. Areas/Identity/Page/Account/Manage/ の _Layout.cshtml で Layout = "/Views/Shared/_Layout.cshtml"; に変更。
  4. Views/Shared/_Layout.cshtml に <partial name="_LoginPartial" /> を追加。

(3) Pomelo.EntityFrameworkCore.MySql

NuGet で Pomelo.EntityFrameworkCore.MySql バージョン 6.0.0 をインストールします。

Pomelo.EntityFrameworkCore.MySql

先の記事で使った MySql.Data.EntityFrameworkCore は非推奨になってました。 代替えパッケージが MySql.EntityFrameworkCore とのことですので、まずそれの最新リリース版 5.0.8 を試してみたのですが、Add-Migration で以下のエラーとなります。

"Method 'AppendIdentityWhereCondition' in type 'MySql.EntityFrameworkCore.MySQLUpdateSqlGenerator' from assembly 'MySql.EntityFrameworkCore, Version=5.0.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' does not have an implementation."

Pomelo.EntityFrameworkCore.MySql には上の画像の通りバージョン 6.0.0 (.NET 6.0 用) がありますのでそれを使ってみたところ、Add-Migration, Update-Database で問題なくデーターベースを生成できました。

 

(4) 接続文字列の変更

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

接続文字列の変更

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

(5) Program.cs の修正

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

サービス登録の変更

Pomelo.EntityFrameworkCore.MySql を使う場合、UseMySQL ではなくて UseMySql であること、引数が異なることに注意してください。詳しくは Pomelo.EntityFrameworkCore.MySql の「2. Services Configuration」のコードを見てください。

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

ここで一旦プロジェクトをリビルドします。リビルドには成功するはずですが、以下の通り警告が 6 つ出ると思います (現時点での話で将来改善されるかも)。

プロジェクトのビルド

上の 2 つは LoginWith2fa.cshtml.cs での using 句のダブり、残り 4 つは NULL 許容参照型がプロジェクト全体で有効化されているものの一部の .cshtml ファイルのソースコードがそれに対応してないことによります。そこを直してもう一度リビルドします。

(7) Add-Migration の実行

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

Add-Migration を実行

上の画像で赤色で反転された文は MySql.EntityFrameworkCore バージョン 5.0.8 を使って失敗した結果のメッセージです。その下が Pomelo.EntityFrameworkCore.MySql バージョン 6.0.0 を使って成功した結果です。

Migrations と言う名前のフォルダとその中に xxxxx_CreateIdentitySchema.cs というファイルが生成されているはずですので確認してください。ファイル名の xxxxx は作成時のタイムスタンプ、CreateIdentitySchema は Add-Migration コマンドで指定した名前です。

(8) Update-Database の実行

次に Update-Database を実行し、Entity Framework Code First の機能を利用して MySQL にデータベース / テーブルを作成します。成功すると、以下のようにデータベースと、必要なテーブルが一式生成されます。coreidentity2 というデータベース名は、上のステップ (4) で接続文字列に指定したものになります。

MySQL データベース

先の記事で問題となった"Specified key was too long; max key length is 3072 bytes" という制約のためエラーになるということはなかったです。

データベース / テーブルは CreateIdentitySchema.cs ファイルのコードに基づいて生成されるのですが、先の記事では主キーの長さが指定されてなかったところが、以下のように varchar(255) に指定されています。

CreateIdentitySchema.cs

utf8mb4 を使用していますので、主キーに設定された varchar(255) は 255 x 4 = 1,020 バイトになります。連結主キーでも制限の 3,072 より小さいので問題ないということのようです。

その後で Visual Studio から MVC アプリを起動しユーザー登録できます。登録したユーザーは上の手順で作成した MySQL データーベースに反映され、登録した ID とパスワードでアプリにログインできるようになります。

Tags: , , ,

CORE

About this blog

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

Calendar

<<  December 2021  >>
MoTuWeThFrSaSu
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar