WebSurfer's Home

Filter by APML

ASP.NET Core Razor Pages で Bootstrap Modal の利用

by WebSurfer 20. February 2026 15:09

データーベースのテーブルの CRUD を行う ASP.NET Core Razor Pages アプリで、ユーザーがデータベースのレコードを削除する際、当該レコードの内容を Bootstrap Modal に表示してユーザーに確認を促し、確認後 Model 上の [Delete] ボタンをクリックするとデータベースのレコードを削除する方法を紹介します。

Index ページでの一覧表示

ベースとしたアプリは Visual Studio 2026 のテンプレートを利用してターゲットフレームワーク .NET 10.0 で作成した Microsoft のチュートリアル「チュートリアル: ASP.NET Core の Razor Pages の概要」のものとほぼ同じで、SQL Server の Movie テーブルの CRUD を行うコードをスキャフォールディング機能を利用して自動生成させたものです。

スキャフォールディング機能を使って実装したコードでは、上の画像の Index ページに一覧表示されている各項目の右横の [Delete] リンクをクリックすると、別ページ Delete に遷移し、遷移した Delete ページ上の [Delete] ボタンをクリックすると SQL Server の Movie テーブルの当該レコードを削除した後 Index ページにリダイレクトされ、削除後の一覧を表示するようになっています。

それを、別ページ Details に遷移しないで、Index ページで下の画像のように Bootstrap Modal 内に削除するレコードの詳細を表示し、ユーザーが確認後 Bootstrap Modal 上の [Delete] ボタンを押すとレコードを削除し、Index ページにリダイレクトして削除後の一覧を表示するようにします。

削除前に Bootstrap Modal で内容を確認

ポイントは、

  • Index ページ上の [Delete] リンクをクリックした時、当該レコードのデータを JavaScript の fetch で取得し、それを Index ページ上で Bootstrap Modal に表示するにはどうするか?
  • Bootstrap Modal 上の [Delete] ボタンをクリックした時、当該レコードを SQL Server の Movie テーブルから削除し、削除後のレコード一覧を Index ページに表示するにはどうするか?

・・・の 2 点です。それを以下に述べます。

Index.cshtml.cs

まず Index.cshtml.cs ですが、スキャフォールディングにより自動生成されたコードに、指定された id のレコード詳細を Bootstrap Modal 内に表示するための部分ビューを返すハンドラ OnGetDetailsAsync と、指定された id のレコードを Movie テーブルから削除するハンドラ OnPostDeleteAsync を追加します。コードは以下の通りです。

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPages.Models;
using RazorPages.Data;

namespace RazorPages.Pages.Movies
{
    public class IndexModel : PageModel
    {
        private readonly TestDatabaseContext _context;

        public IndexModel(TestDatabaseContext context)
        {
            _context = context;
        }

        public IList<Movie> Movie { get; set; } = default!;

        public async Task OnGetAsync()
        {
            Movie = await _context.Movies.ToListAsync();
        }

        // 指定された id のレコード詳細を Bootstrap Modal 内に表示する
        // ための部分ビューを返すハンドラ
        public async Task<IActionResult> OnGetDetailsAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var movie = await _context.Movies
                              .FirstOrDefaultAsync(m => m.Id == id);

            if (movie is not null)
            {
                return Partial("_Details", movie);
            }

            return NotFound();
        }

        // 指定された id のレコードを Movie テーブルから削除するハンドラ
        // 削除後 Index ページにリダイレクト
        public async Task<IActionResult> OnPostDeleteAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var movie = await _context.Movies.FindAsync(id);
            if (movie != null)
            {                
                _context.Movies.Remove(movie);
                await _context.SaveChangesAsync();
            }

            return RedirectToPage("./Index");
        }
    }
}

Index.cshtml

上の Index.cshtml.cs に対応するビュー Index.cshtml ですが、スキャフォールディングにより自動生成されたコードに以下の追加・修正を行います。

  1. Bootstrap Modal のコードを追加。
  2. form 要素を追加しその中に隠しフィールドを配置。隠しフィールドはユーザーが選択したレコードの id をサーバーに送信するために用いる。
  3. id を引数にとる JavaScript 関数 openModal を定義。openModal が呼ばれると、fetch API でサーバーに要求を出し、応答として返された部分ビューの html を Bootstrap Modal 内の div 要素に書き込んだ後、引数 id の値を隠しフィールドに書き込み、Bootstrap Modal を表示する。
  4. Delete リンクを上の JavaScript 関数 openModal を呼び出すよう変更する。Model から当該レコードの Id を取得してそれを openModal の引数 id に渡す。
  5. Bootstrap Modal の [Delete] ボタンの click イベントにリスナーを設定し、リスナーで上記 2 で追加した from を submit する。

コードは以下の通りとなります。コメントの ( ) 内の数字は上の 1 ~ 5 に該当します。

@page
@model RazorPages.Pages.Movies.IndexModel

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

<!-- (1) Bootstrap Modal のコードを追加 -->
<div class="modal fade" id="staticBackdrop"
     data-bs-backdrop="static" data-bs-keyboard="false"
     tabindex="-1" aria-labelledby="staticBackdropLabel"
     aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title"
                    id="staticBackdropLabel">
                    Movie to be deleted
                </h5>
                <button type="button" class="btn-close"
                        data-bs-dismiss="modal"
                        aria-label="Close">
                </button>
            </div>
            <div class="modal-body" id="details">
                ...
            </div>
            <div class="modal-footer">
                <button type="button"
                        class="btn btn-primary"
                        data-bs-dismiss="modal">
                    Cancel
                </button>
                <button type="button"
                        id="confirmedDelete"
                        class="btn btn-danger">
                    Delete
                </button>
            </div>
        </div>
    </div>
</div>

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Movie)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Title)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.ReleaseDate)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Genre)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Price)
                </td>
                <td>
                    <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.Id">Details</a> 
|
                    @* (4) Delete リンクを JavaScript 関数 openModal を呼び出す
                           よう変更。当該レコードの Id を取得してそれを openModal 
                           の引数 id に渡す *@
                    <a href="javascript:void(0);" 
                        onclick="javascript:openModal('@item.Id')">
                        Delete
                    </a>
                </td>
            </tr>
        }
    </tbody>
</table>



<!-- 
    (2) form 要素を追加し中に隠しフィールドを配置。隠しフィールドは
    ユーザーが選択したレコードの id をサーバーに送信するために用い���。
    asp-page-handler="Delete" の設定により form が submit され
    ると Index.cshtml.cs の OnPostDeleteAsync が呼び出される
-->
<form method="post" id="myForm" asp-page-handler="Delete">
    <input type="hidden" name="id" id="hiddenField" />
</form>

@section Scripts {
    <script>
        $(document).ready(function () {
            // staticBackdrop は Bootstrap Modal の div 要素の id
            const modal = document.getElementById('staticBackdrop');
            const confirmationModal = new bootstrap.Modal(modal);
            const confirmActionBtn = document.getElementById('confirmedDelete');
            const myForm = document.getElementById('myForm');
            const hiddenField = document.getElementById('hiddenField');

            // (3) id を引数にとる openModal 関数を定義
            window.openModal = async (id) => {
                // fetch を使ってサーバーにリクエストを送るための url 作成
                // handler=details でハンドラ OnGetDetailsAsync を指定
                // 引数 id をクエリ文字列に追加
                const url = "/Movies?handler=details&id=" + id;

                // Movie 詳細を表示する Bootstrap Modal 内の div 要素を取得
                const detailsDiv = document.getElementById('details');

                // Fetch を使って url にリクエストを送りレスポンスを受け取る
                const response = await fetch(url);

                if (response.ok) {
                    // レスポンスのテキスト(部分ビューが返す html)を取得
                    const text = await response.text();

                    // テキストを Bootstrap Modal 内の div 要素に書き込む
                    detailsDiv.innerHTML = text;

                    // 引数 id の値を隠しフィールドの value に書き込む
                    hiddenField.value = id;

                    // Bootstrap Modal を表示
                    confirmationModal.show();
                }
            }

            // (5) Bootstrap Modal の [Delete] ボタンの click イベントに
            // リスナーを設定
            confirmActionBtn.addEventListener('click', function () {
                // [Delete] ボタンのクリックで上の form を submit する。
                // form 要素で asp-page-handler="Delete" と設定されている
                // ので Index.cshtml.cs の中の OnPostDeleteAsync ハンド
                // ラに POST 要求がかかる
                myForm.submit();
            });
        });
    </script>
}

Bootstrap Modal について以下に補足説明を書いておきます。

Visual Studio 2026 のテンプレートで、ターゲットフレームワーク .NET 10.0 として作成した ASP.NET Core Razor Pages アプリのプロジェクトには Bootstrap.js, Boorstrap.css v5.3.3 が含まれています。Bootstrap Modal を使うために必要なものはそれらの中に含まれています。(.NET Core 3.1 ~ 5.0 は v4.3.1、.NET 6.0 ~ 8.0 は v5.1 とバージョンが異なりますので注意してください)

Bootstrap Modal の詳しい説明は Bootstrap 5 のドキュメント「Modal (モーダル)」にあります。普通の html + css + javascript のページに Bootstrap Modal を組み込むなら、そのドキュメントから十分な情報が得られると思います。

上のコードの Modal の部分は Bootstrap 5 のドキュメント「Modal (モーダル)」の「Static backdrop」セクションのサンプルコードをコピペし、若干修正をして利用しています。詳細を書き込む div 要素に id="details" を追加したこと、フッター部の 2 つのボタンをキャンセル用と削除用に変更したことが主な違いです。


_Details.cshtml

Index.cshtml.cs のハンドラ OnGetDetailsAsync が呼び出す部分ビュー _Details.cshtml は以下の通りです。この _Details.cshtml が返す html が Bootstrap Modal 内に書き込まれます。

@model RazorPages.Models.Movie

<h4>Are you sure you want to delete this?</h4>

<div>
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.ReleaseDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.ReleaseDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Genre)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Genre)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Price)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Price)
        </dd>
    </dl>
</div>

最後に、この記事を書くのに参考にした ASP.NET Core Razor Pages 関係の Microsoft のドキュメントへのリンクを載せておきます。

翻訳がダメなところがありますので、意味不明の場合は英語版を読むことをお勧めします。

Tags: , , , ,

CORE

GitHub Copilot Next Edit Suggestions

by WebSurfer 16. February 2026 17:24

Visual Studio 2026 の AI 駆動開発において、GitHub Copilot が提供するエディタ上でのインライン提案には「入力候補 (Completions)」と「次の編集候補 (Next edit suggestions)」という 2 種類があります。この記事では後者の「次の編集候補」について書きます。

Next edit suggestions の有効化

前者の「入力候補」については、先の記事「Visual Studio 2026 AI 駆動開発 - コード補完」に書きましたので、そちらを見てください。

「入力候補」と「次の編集候補」の何が違うかを簡単に書くと以下の通りです:

  • 入力候補: 現在のカーソル位置で、ユーザー入力に基づき、続きのコードを予測して提案する
  • 次の編集候補: ある行でのコードの変更に関連して、別の行で必要になる変更を予測して提案する。提案に従って、Tab キーを連続して押していくだけで、複数箇所にわたる修正が可能になる

詳しい説明は Microsoft のドキュメント「GitHub Copilot の入力候補を始めてみましょう」、「GitHub Copilot の次の編集提案を使い始める方法」にありますので見てください。

自分の環境の Visual Studio 2026 + GitHub Copilot Free で「次の編集候補」を有効にして使ってみましたので、その時の状況を以下に書いておきます。

まず、「次の編集候補」はデフォルトでは無効になっていますので、この記事の一番上の画像で示したように、GitHub Copilot のメニューを開いて Settings > Enable Next Edit Suggestions にチェックを入れて有効化します。

次に、Visual Studio 2026 のテンプレートを使ってターゲットフレームワーク .NET 10.0 でコンソールアプリを作成し、それに以下の Point クラスを書いて試してみました。(Microsoft のドキュメントの「意図の変更に合わせる」のコードとほぼ同じです)

Point クラス

上の Point クラスの private フィールド x, y を public プロパティ X, Y に変更してみます。113 行目で修正提案が出ているので Tab キーを押して提案を Accept します。

private フィールド x, y を public プロパティ X, Y に変更

すると以下の画像のように表示されます。現在のカーソルは 113 行目にあり、「次の編集候補」は 117 行目にありますので、[Tab] の横に下向きの ↓ が表示されています。Tab キーを押すと「次の編集候補」の 117 行にカーソルが移動します。

「次の編集候補」の表示

117 行の修正案が OK なら再度 Tab キーを押して修正案を Accept します。118, 123, 124 行も修正が必要ですが、同様に Tab キーを押していけば下の画像のように修正が完了します。

Tab キーを押していき修正完了

以上が「次の編集候補」により、複数の行に渡って修正提案を受けながら、それらを順次適用していく場合の典型的な動きになるようです。


ただ、エディタ上でいろいろ変更しながら「次の編集候補」の出方を見ていると、同じことを繰り返しても結果が異なるということがありました。このことは気を付けた方が良さそうです。具体的にどういう事かを以下に書きます。

例えば、Microsoft のドキュメントの「ミスをキャッチして修正する」のセクションに書いてある bol ⇒ bool の修正提案ですが、最初はドキュメントに書いてある通りに提案されるものの、何度か同じことを試すと提案が出てこなくなります。bol としたのはユーザーの意図と誤解するのか、bol 型を新たに作るという提案がされます。

また、「意図の変更に合わせる」のセクションに書いてあるクラス Point を Point3D に変更する場合ですが、最初はドキュメントに書いてあるように、一行ずつ順に修正提案が出て、それを見ながら修正して行くという形になるものの、同じことを繰り返し試すと、Point ⇒ Point3D とタイプした時点で即以下の画像のようになります。

クラス Point を Point3D に変更

ネットの記事「GitHub Copilot NESの内部実装が公開」に "Copilot NESはユーザーのエディタ上の「操作履歴」を記録します。操作履歴のデータを専用モデルに送信して「続きの操作」を推論します" と書いてあるのを見つけました。それが正しいとすると、想像ですが、エディタ上でいろいろ変更しながら試していた時の「操作履歴」が影響していたのかもしれません。

Tags: , , ,

DevelopmentTools

Visual Studio 2026 で Docker コンテナー使用

by WebSurfer 8. February 2026 13:51

Visual Studio 2026 で Docker コンテナー を使うため、Microsoft のドキュメント「Visual Studio コンテナー ツール」を参考に、環境を構築してみました。

簡単にできるのかと思いましたが、使えるようになるまでいろいろ紆余曲折がありました。将来同じ作業をすることがあるかもしれませんので、また無駄な労力と時間を使わないで済むよう、その顛末や色々調べたことを備忘録として残しておくことにしました。(自分は今まで Linux とか Docker とかは触ったこともない素人ですので間違いがあるかもしれませんのでご注意)

下の画像は、Docker Desktop を起動した後、Docker コンテナーのサポートを有効にした ASP.NET Core Razor Pages アプリを Visual Studio 2026 で開いた時の Docker Desktop の表示です。

Docker Desktop の表示

上の画像の意味は、以下のレイヤーが構築されて、Linux の Docker コンテナーで RazorPagesDocker という名前の ASP.NET Core Razor Pages アプリを動かすための準備が整ったということです。

  1. Windows 10
  2. WSL2(Linux 仮想マシン)
  3. Docker Desktop(Docker Engine を WSL2 上で動かす。Docker Engine が Linux イメージを使ってコンテナーを作成)
  4. Docker コンテナー(ASP.NET Core アプリが動く場所)

この状態から Visual Studio を操作してアプリを実行すると、Windows OS 上で Kestrel を使った時と同様に、ブラウザが立ち上がってページが表示されます。

Visual Studio 2026 で Docker コンテナーを使うために必要なのは、(1) Docker Desktop をダウンロードしてインストールし動くように設定する、(2) プロジェクトにコンテナーのサポートを追加するという 2 点です。

WSL2 も必要ですが、これは自分の環境の Windows 10 Pro には含まれています。既定では無効になっていますが、Docker Desktop をインストールすると WSL2 を自動的に有効化し、Linux カーネルをセットアップするそうです。

Ubuntu などの Linux ディストリビューションをインストールする必要はありません。Docker Desktop をインストールすると WSL2 用の docker-desktop という専用の軽量ディストリビューションを自動生成するそうです。PowerShell からコマンド wsl --list --verbose でインストールされている Linux ディストリビューションを確認できます。下の画像のように docker-desktop と表示されるはずです。

Linux ディストリビューション

Dockerfile → Docker Desktop → WSL2 → Docker コンテナー作成の流れは以下の通りです。Visual Studio で Docker サポートを有効にすると:

  1. Visual Studio が Dockerfile を生成
  2. Visual Studio が Docker Desktop にビルドを依頼。 Docker Desktop は WSL2 内の Docker Engine に処理を渡す(Docker Desktop 自体は Windows アプリですが、Docker Engine は WSL2 の Linux 上で動いています)
  3. Docker Engine が Linux イメージを使ってコンテナーを作成
  4. コンテナー内で ASP.NET Core アプリが実行される

無知な自分がトラブったのは、Docker Desktop をインストールし、起動しようとしたら "Virtualization support not detected" というエラーとなって、Docker Desktop を開始できなかったことでした。調べてみるとハードウェアと Windows OS レベルで仮想化支援機能を有効にする必要があるとのこと。以下のようにして解決しました。

(1) BIOS 設定

自分の Windows 10 Pro の BIOS 設定の場合ですが、Advanced / CPU Configuration 下の項目 Intel (VMX) Virtualization Technology を Enabled に設定します。(Intel VT-x が Supported と表示されていたので、デフォルトで設定済みかと誤解していたのですが、サポートされてはいても有効になってなかったということでした。お粗末)

(2) Windows の機能の有効化

Windows の機能の有効化または無効化で、以下の画像のように「Hyper-V」の全項目、「Linux 用 Windows サブシステム」、「仮想マシンプラットフォーム」にチェックを入れて有効化します。上の BIOS 設定ができてないと「Hyper-V Hypervisor」がグレーアウトされていてチェックできないはずです。

Windows の機能の有効化

(3) WSL2 用 Linux カーネルの更新

自分の環境では WSL2 用 Linux カーネルを更新する必要がありました。管理者権限で立ち上げた PowerSell でコマンド wsl --update を実行します。

自分の環境では、コマンド wsl --update 実行後の WSL2 のバージョンは以下の通りとなりました。(実行前は不明)

WSL2 のバージョン情報

この状態で、何故か Windows Update が Windows Subsystem for Linux Update 5.10.102.1 を適用しようとして失敗します。wsl --update で 6.6.87.2-1 にしたのに 5.10.102.1 を適当しようとして失敗した様子。wushowhide.diagcab を使って隠すことにしました。


以上の操作で仮想化支援機能は有効になり、Task Manager のパフォーマンス > CPU タブを開くと、仮想化が有効と表示されているはずです。

仮想化が有効

上記で仮想化支援機能を有効にできたら、Docker Desktop を立ち上げて、Docker コンテナーのサポートを有効にした ASP.NET Core アプリを Visual Studio 2026 で開くと、この記事の一番上の画像のように Docker Desktop に表示されます。

そうならない場合は、下の画像のように Visual Studio 2026 で「Container (Dockefile)」が選択されていることを確認してください。

Container (Dockefile) の選択

上の画像の項目「Container (Dockefile)」は、プロジェクトのコンテナーサポートが有効になってないと表示されませんので注意してください。下の画像は Visual Studio のテンプレートを使ってプロジェクトを作成する際にコンテナーサポートを有効にするための設定です。

コンテナーサポートを有効にするための設定

ここまでできれば、Visual Studio で Windows OS 上で Kestrel を使って開発しているときと同様に、Docker コンテナの Linux OS 上で ASP.NET Core アプリを開発できるはずです。

最後に、Docker Desktop を停止する方法を書いておきます。起動した Docker Desktop は、Visual Studio を閉じても Docker Desktop 画面を閉じても動いています。停止するには、下の画像のように、Windows タスクバーのメニューバーにあるクジラアイコンを右クリックし「Quit Docker Desktop」をクリックします。

Docker Desktop を停止

Tags: , ,

DevelopmentTools

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。ブログ2はそれ以外の日々の出来事などのトピックスになっています。

Calendar

<<  March 2026  >>
MoTuWeThFrSaSu
2324252627281
2345678
9101112131415
16171819202122
23242526272829
303112345

View posts in large calendar