
久し振りにプログラミングを開始した。
ASP.NET Core MVCを使ってウェブサイトを作ってみる事にしたのだ。
どんなウェブサイトかと言うと、ある種の掲示板的なサイトだ。
ワテの場合、従来のプログラミング作業では自分の知らない文法や手法などは、書籍を読むとかネット検索して正解を見付けていた。
しなしながら、必死でネット検索しても自分が探している情報がヒットしない事もある。数時間Google検索しまくっても見付からない、そう言う事も有り得る。
ところが、最近では生成AIさんと対話する事で短時間に的確に正解を教えて貰うことが出来るのだ。
と言う事で、当記事ではVisual Studio 2022 Currentを使ってASP.NET Core MVCのプログラミングをしている時に、生成AIのChatGPTさんとの会話で教えてもらった情報をまとめてみた。
要するにワテの単なる備忘録だが。
では、本題に入ろう。
C#関連
LINQでラムダ式を省略して簡素に書けるがNull許容性の警告が出る
例えばワテは以下のようなLINQやその中でラムダ式を良く使っている。
List<string> imageFiles = Directory.GetFiles(imageFolderPath) .Select(filePath => Path.GetFileName(filePath)) .ToList();
ところがChatGPTさんとの会話の中で、上のコードは以下のようにラムダ式を省略した形に書き換えても良い事を教えて貰った。
List<string> imageFiles = Directory.GetFiles(imageFolderPath) .Select(Path.GetFileName) .ToList();
確かに上のコードは簡素になった。
ただし、一つ前のラムダ式タイプのコードを書き慣れているワテにしてみれば、上コードの .Select(Path.GetFileName) の引数に行き成り登場した Path.GetFileName は一体全体どこから出てきたのか?と焦ったが。
さらに、上コードを採用した結果、以下の警告が出るようになったのだ。
'List<string?>' 型の値における参照型の Null 許容性が、対象の型 'List<string>' と一致しません。
この警告の意味としては左辺はList<string>だが、右辺はList<string?>になる可能性があるので、それで警告が出ているようだ。
警告なので無視しても実用上は問題は無いが、何となく気になる。
このエラー解消方法をChatGPTに質問したが、ChatGPTさんの回答を試しても警告が解消しない。
そこでワテが必死でGoogle検索した結果、以下のように修正したら警告は解消した。
List<string> imageFiles = Directory.GetFiles(imageFolderPath) .Select(Path.GetFileName) .WhereNotNull() .ToList();
ここで上コード右辺に登場する .WhereNotNull() は以下のように適当な名前のstaticクラスで拡張メソッドとして定義している。
static class MyExtensions1 { public static IEnumerable WhereNotNull(this IEnumerable<T?> source) where T : class { if (source == null) { return Enumerable.Empty(); } return source.Where(x => x != null)!; } }
上記のコードはこのサイトを参考にさせて頂いた。
要するにnullの要素を取り除く処理を入れれば良いのだ。なるほど。
あるいは、上サイトによれば以下のようにしても良いらしい。
static class MyExtensions2 { public static IEnumerable WhereNotNull(this IEnumerable<T?> source) where T : class // ReSharper disable once RedundantEnumerableCastCall => source.OfType(); }
確かにこれでも行けたのだが、ワテは理解出来ていない。
Entity Framework関連
データーベースのMigrationを行う場合はプロジェクトが正常にビルド出来る事が必須
Visual Studioが標準で用意しているASP.NET Core MVCのテンプレートを使えばウェブサイトが簡単に作れる。ワテが過去に作成したサイトはここにある。
Entity Frameworkを組み合わせればデーターベースを検索するなどの機能を持つウェブサイトも簡単に作れる。例えば以下のサイトはSQL Server Expressを使っている。
さて、そんな便利なEntity Frameworkだが、ウェブサイトの開発段階ではデーターベースのテーブルの元になっているモデルクラスのメンバを変更する事は良くある。
例えば現状では以下のPersonモデルクラスを使ってデーターベースにテーブルが作成されているとする。
public class Person { [Key] public int Id { get; set; }// 自動生成されたプライマリキー public string? Name { get; set; } // 名前 }
このモデルクラスに新たに2つのプロパティを追加して以下のように変更した。
public class Person { [Key] public int Id { get; set; }// 自動生成されたプライマリキー public string? Name { get; set; } // 名前 public string? Address { get; set; } // 住所(必要なら) [RegularExpression(@"^\+?[0-9]{10,15}$", ErrorMessage = "電話番号は正しい形式で入力してください。")] public string? Tel { get; set; } }
この変更をデーターベーステーブルに反映させるにはマイグレーションの機能を使う。
このマイグレーションを実行する場合には、今開発しているプロジェクトが正常にビルド出来る事が必須だ。
つまり、以下のようにdotnet buildを実行して正常終了しなくてはならない。この場合はPMコンソール(Package Manager Console)から実行しているが、PowerShellウインドウ(Visual StudioでCTRL+@か[表示]→[ターミナル]メニューを開く)から実行しても良い。
PMコンソールの文字化けを解消する
ところが、Visual Studio 2022 Current版の場合、PMコンソールに表示されるメッセージが文字化けするのだ。この症状は過去のVS2019の頃にも経験した。
PM> dotnet build 蠕ゥ蜈・ッセ雎。縺ョ繝励Ο繧ク繧ァ繧ッ繝医r豎コ螳壹@縺ヲ縺・∪縺・.. 蠕ゥ蜈・ッセ雎。縺ョ縺吶∋縺ヲ縺ョ繝励Ο繧ク繧ァ繧ッ繝医・譛譁ー縺ァ縺吶・ C:\Users\wate\.nuget\packages\microsoft.typescript.msbuild\5.8.1\tools\Microsoft.TypeScript.targets(485,5): error MSB6006: "C:\Program Files\nodejs\node.exe" 縺ッ繧ウ繝シ繝・1 繧剃シエ縺」縺ヲ邨ゆコ・@縺セ縺励◆縲・[E:\Desktop\MY_site\[省略]CoreMVC.csproj] 繝薙Ν繝峨↓螟ア謨励@縺セ縺励◆縲・ C:\Users\wate\.nuget\packages\microsoft.typescript.msbuild\5.8.1\tools\Microsoft.TypeScript.targets(485,5): error MSB6006: "C:\Program Files\nodejs\node.exe" 縺ッ繧ウ繝シ繝・1 繧剃シエ縺」縺ヲ邨ゆコ・@縺セ縺励◆縲・[E:\Desktop\MY_site\[省略]CoreMVC.csproj] 0 蛟九・隴ヲ蜻・ 1 繧ィ繝ゥ繝シ 邨碁℃譎る俣 00:00:03.48 繝ッ繝シ繧ッ繝ュ繝シ繝峨・譖エ譁ー縺悟茜逕ィ蜿ッ閭ス縺ァ縺吶りゥウ邏ー縺ォ縺、縺・※縺ッ縲~dotnet workload list` 繧貞ョ溯。後@縺ヲ縺上□縺輔>縲・
それで今回ChatGPTさんにPMコンソールの文字化け解消方法を質問したところ、解決方法が分かった。
まず一つ目の対策はコードページを以下のように65001つまりUTF-8に変更すれば良いとの事だ。
PM> chcp 65001 Active code page: 65001
ところがこのようにコードページを設定しても dotnet build のメッセージの文字化けは解消しなかった。
次に教えて貰った対策が、以下のように出力エンコーディングを設定する手法で、この手法を使うと以下のようにdotnet buildのメッセージ文字化けが解消出来た。
PM> $OutputEncoding = [Console]::OutputEncoding = [Text.Encoding]::UTF8 PM> dotnet build 復元対象のプロジェクトを決定しています... 復元対象のすべてのプロジェクトは最新です。 C:\Users\wate\.nuget\packages\microsoft.typescript.msbuild\5.8.1\tools\Microsoft.TypeScript.targets(485,5): error MSB6006: "C:\Program Files\nodejs\node.exe" はコード 1 を伴って終了しました。 [E:\Desktop\MY_site\[省略]CoreMVC.csproj] ビルドに失敗しました。 C:\Users\wate\.nuget\packages\microsoft.typescript.msbuild\5.8.1\tools\Microsoft.TypeScript.targets(485,5): error MSB6006: "C:\Program Files\nodejs\node.exe" はコード 1 を伴って終了しました。 [E:\Desktop\MY_site\[省略]CoreMVC.csproj] 0 個の警告 1 エラー 経過時間 00:00:03.78 ワークロードの更新が利用可能です。詳細については、`dotnet workload list` を実行してください。 PM> Get-Command node CommandType Name Version Source ----------- ---- ------- ------ Application node.exe 10.15.0.0 C:\Program Files\nodejs\node.exe PM> where node
PMコンソールでdotnet buildが失敗する問題はNode.jsアップデートで解決
さて、文字化けは解消出来たのだがビルドに失敗している。
ところがVisual Studioの画面でマウス操作でプロジェクトのビルドを実行すると正常にビルド出来るのだが。
この点をChatGPTさんに質問したところ、以下の回答だった。
Node.js に関連するエラー(TypeScript のビルドに失敗している可能性が高い)
Visual Studio では問題ないのに dotnet build ではエラーが出るのは、環境変数や実行される MSBuild のバージョンが異なることが原因かも。
Node.jsのバージョンは以下のコマンドで確認出来る。
PM> node -v v10.15.0 PM>
バージョン v10.15.0 はかなり古い。
Windows10のコントロールパネルで「プログラムと機能」の画面でNode.jsで検索したら2019/1/9にインストールしたと出ているので、数年前にどうやらワテが自分でインストールしたようだ。
そこで、Node.js 公式サイトから LTS(Long Term Support)推奨版をダウンロードしてインストールした。
その結果、無事にdotnet buildが成功するようになった。
PM> node -v v22.14.0 PM> dotnet build 復元対象のプロジェクトを決定しています... 復元対象のすべてのプロジェクトは最新です。 TestSearchCoreMVC -> E:\Desktop\MY_site\[省略]CoreMVC.dll ビルドに成功しました。 0 個の警告 0 エラー 経過時間 00:00:09.60 ワークロードの更新が利用可能です。詳細については、dotnet workload list を実行してください。 PM>
ワテも一つ賢くなったぞ。それにしてもChatGPTさんは優秀だ。
データーベースのマイグレーションを実行する
これでようやくモデルクラスの変更をテーブルに反映出来る。
PM>Add-Migration ChangeClassModel
上記コードを実行すると、Migrationsフォルダに新たに以下のようなファイルが自動生成される。
20250311180359_ChangeClassModel.cs
このファイルの中身はデーターベースに対する今回の変更操作を行う命令が書いてある。中身の詳しい意味はワテは理解出来ていないが。
そして、このマイグレーションを適用するなら以下のコマンドを実行する。
PM>Update-Database
このコマンドがエラー無しに成功したら、無事にテーブルが更新される。
この場合、既存のテーブルにデータが入っていても削除されてしまうので、必要なら事前にバックアップするなどしておけば良い。
なお、Migrationsフォルダに古いマイグレーションファイルが残っていると、何らかの競合が起こってマイグレーション作業に失敗する事もあるので、もし必要なら事前に既存のマイグレーションを削除してから新しいマイグレーション操作をやるのも良い。
マイグレーションの削除は以下の通り。
PM>Remove-Migration
あるいは手作業でMigrationsフォルダの中身をすべて削除しても良い。
これで無事にモデルクラスに加えた変更がデーターベーステーブルに反映させることが出来た。
こう言うのはコードファーストって言うのかな。
ASP.NET Core関連
TempDataとViewDataの違い
ワテはTempData、ViewData、ViewBagの違いが良く分かっていない。どれも似たようなもんだと思っていた。
ワテの場合はこれら三つの仕組みのうち、ViewDataを何となく使っている。
例えば以下のようにController側のアクション関数内でViewDataに文字列を保管する。
ViewData["Message"]="ビューに持って行きたいメッセージ"; return View(person);
こうすればView側でこの文字列を取り出すことが可能だ。
ところが以下のようにリダイレクトした場合にはこの方法だとEdit.cshtmlビューでViewDataの中身を取り出してもnullになってしまう。
ViewData["Message"]="ビューに持って行きたいメッセージ"; return RedirectToAction("Edit", new { id = id, page = page });
ChatGPTさんによると、リダイレクトすると新しいリクエスト が発生するため、ViewData の内容はリセットされるとの事だ。
ところがTempDataはリダイレクト後もデータを保持できる(ただし、一度アクセスすると消える)との事だ。
TempData["Message"] = "ビューに持って行きたいメッセージ"; return RedirectToAction("Edit", new { id = id, page = page });
そして、Edit.cshtmlではTempDataからデータを取り出せるのだ。
@{ var message = TempData["Message"]; } <p>@message</p>
なお、TempDataは 一度取得すると消えるので、もし次のリクエストでも使いたい場合は以下のようにTempData.Keep() を使う。
var message = TempData["Message"]; TempData.Keep("Message");
こうすると、次のリクエストでも TempData[“Message”] を保持できる。
まとめると以下の通り
- ViewData はリダイレクト後に消える
- リダイレクト後もデータを保持するには TempData を使う
- TempData.Keep() を使うと次のリクエストでも保持できる
そう言う事か。
正規表現を使ったフォーム入力検証機能が便利
モデルクラスにTelプロパティを追加して、以下のように[RegularExpression]属性も追加して正規表現を使った入力検証機能を使いたい。
[RegularExpression(@"^\+?[0-9]{10,15}$", ErrorMessage = "電話番号は正しい形式で入力してください。")] public string Tel { get; set; }
この正規表現では、電話番号が+付きで10桁以上15桁以下の数字であることを確認する。
<form>の中に<input>タグを置いてTelデータを入力するRazorビューを作成する場合は、以下の行をRazorビュー末尾に追加しておけば良い。
@section Scripts { @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } }
このパーシャルビューはクライアントサイドでフォームバリデーションを行うために必要なJavaScriptライブラリ(jquery.validate.min.js と jquery.validate.unobtrusive.min.js)が含まれている。
あとは<form>の中に以下のような感じで<input>タグを埋め込めば、入力時の検証機能が動作する。
<div class="form-group"> <label asp-for="Tel" class="control-label"></label> <input asp-for="Tel" class="form-control" /> <span asp-validation-for="Tel" class="text-danger"></span> </div>
実際に、この部分をブラウザーF12で確認したら、以下のように確かに正規表現が埋め込まれている。
<input class="form-control input-validation-error" type="text" data-val="true" data-val-regex="電話番号は正しい形式で入力してください。" data-val-regex-pattern="^\+?[0-9]{10,15}$" id="Tel" name="Tel" value="" aria-describedby="Tel-error" aria-invalid="true">
そう言う事か。
TypeScript関連
TypeScriptの型チェックでエラーが出た場合の対策
ワテはJavaScriptよりもTypeScriptを好んで使う。
その理由はTypeScriptなら型や文法のチェックが厳密なので。
でも、即席でササッとJavaScriptでコードを書く事もある。それらの即席JavaScriptコードをTypeScriptファイルに移動すると、TypeScriptの厳密性ゆえに警告やエラーが出ることがある。
まあ、それは良いことなのだが、ワテの場合、エラーを解消するのに手間取ることも多い。
ところが今ではChatGPTさんが心強い助っ人なので、何の心配もない。
例えば、以下のJavaScriptをTypeScriptファイルfile1.tsに移動したら、
document.getElementById("global-password").addEventListener("focus", function () { this.type = "text"; });
以下のエラーが出る。
(TS) Property 'type' does not exist on type 'HTMLElement'.
この理由や解決方法をChatGPTさんに聞いたら、直ぐに教えてくれた。
エラーの原因は document.getElementById(“global-password”) の戻り値が HTMLElement | null であり、HTMLElement には type プロパティがないことです。
解決策としては、次の2つがあるとの事だ。
① 型アサーション(as HTMLInputElement を使う)
document.getElementById("global-password")?.addEventListener("focus", function () { (this as HTMLInputElement).type = "text"; });
as HTMLInputElement を使うことで、this を HTMLInputElement として扱えるようになる。
② 実行時チェック(instanceof を使う)
const passwordInput = document.getElementById("global-password"); if (passwordInput instanceof HTMLInputElement) { passwordInput.addEventListener("focus", function () { passwordInput.type = "text"; });
instanceof を使うと、実行時に passwordInput が HTMLInputElement かどうかを確認できる。
どっちを使うか迷ったが、ChatGPTさんの意見は以下の通り。
どちらを使ってもOKですが、 「型アサーション」はコンパイル時のチェックだけで、「実行時チェック」は実際に要素の型を確認する」 という違いがあります。
安全性を重視するなら instanceof を使うのがベストですが、コードを短く書きたい場合は as HTMLInputElement で十分です!
と言う事で、ワテのアバイは後者(実行時チェック instanceof )を使うことにした。
なお、前者の型アサーション(as HTMLInputElement)を使う方法を以下に再掲載する。
document.getElementById("global-password")?.addEventListener("focus", function () { (this as HTMLInputElement).type = "text"; });
ここで以下の記号が気になる。
?.
まるでC#の「null 条件演算子(?.)」とソックリだ。
ChatGPTさんに質問した所、
?. は オプショナルチェーン(Optional Chaining) という演算子で、C# の 「null 条件演算子(?.)」 と同じような動作をします。
との事だ。そう言う事か。
要するに、このコードは以下の動作になる。
document.getElementById("global-password") が null でない場合 → .addEventListener() を実行 document.getElementById("global-password") が null の場合 → .addEventListener() を実行せず、安全にスキップ
つまり、nullに対して.addEventListener() を実行するとエラー (Cannot read properties of null) になるので、それを回避する安全策だ。
TypeScriptにおけるオプショナルチェイニング(?.)演算子は、TypeScript 3.7で導入されたらしい。
tsconfig.json を最新の設定に更新
TypeScriptファイルに関するコンパイル設定はtsconfig.jsonで行う。
従来のワテはどこかで入手した以下の設定を使っていたのだが、これだと古いes5をターゲットにするのでコンパイルで生成されるJavaScriptコードが古い仕様になるようだ。
{ "compileOnSave": true, "compilerOptions": { "noImplicitAny": false, "noEmitOnError": true, "removeComments": false, "sourceMap": true, "target": "es5", "outDir": "wwwroot/js" }, "include": [ "myTS/**/*" ] }
ChatGPTさんのお勧めの最も推奨される設定(最新の仕様を使えるようにする)は以下の通り。
{ "compileOnSave": true, "compilerOptions": { "noImplicitAny": false, "noEmitOnError": true, "removeComments": false, "sourceMap": true, "target": "esnext", // これが最新 "lib": ["esnext", "dom"], // 最新のECMAScript & DOM機能を使う "outDir": "wwwroot/js" }, "include": [ "myTS/**/*" ] }
取り敢えずこの設定を使っているが詳しいことは理解出来ていない。
SQL文
ワテの場合、データーベースの操作は、SQL Server Management Studioを使っている。
でもSQL文は滅多に書かないので文法が覚えられない。
でも大丈夫。
そんな時にはChatGPTさんだ!
Personテーブルの49<= Id <= 55 の範囲のIdのパスワードを1234にするSQL文は?
と質問したら、
ChatGPTの回答は以下の通り。
UPDATE [PersonCoreMVC_DB].[dbo].[Person] SET [Password] = '2222' WHERE [Id] BETWEEN 49 AND 55;
あるいは以下の記述でも良い。
UPDATE [PersonCoreMVC_DB].[dbo].[Person] SET [Password] = '2222' WHERE 49<= [Id] AND [id] <= 55;
注意事項としては、当然ながらWHERE 条件を忘れるとすべてのレコードのパスワードが変更されるので注意が必要だ。
まとめ

久し振りのプログラミングだ。
数年前はプログラミングに熱中していたが最近は木工DIYや電子工作が多かった。
今後はどちらかに偏るのでは無く両立して行きたい。
当記事ではASP.NET Core MVCのウェブサイトを久し振りに開発を始めたワテが、開発途中で直面した様々な問題をChatGPTさんの助けを借りながら学習した内容を備忘録としてまとめてみた。
今後も、同じように学習した内容を備忘録として当記事へ追加あるいは別記事にして行く予定だ。
さて、一体全体どんな物凄いウェブサイトが出来るのか!?
(続く)
コメント