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

ベースとしたアプリは 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 ページにリダイレクトして削除後の一覧を表示するようにします。

ポイントは、
-
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 ですが、スキャフォールディングにより自動生成されたコードに以下の追加・修正を行います。
-
Bootstrap Modal のコードを追加。
-
form 要素を追加しその中に隠しフィールドを配置。隠しフィールドはユーザーが選択したレコードの id をサーバーに送信するために用いる。
-
id を引数にとる JavaScript 関数 openModal を定義。openModal が呼ばれると、fetch API でサーバーに要求を出し、応答として返された部分ビューの html を Bootstrap Modal 内の div 要素に書き込んだ後、引数 id の値を隠しフィールドに書き込み、Bootstrap Modal を表示する。
-
Delete リンクを上の JavaScript 関数 openModal を呼び出すよう変更する。Model から当該レコードの Id を取得してそれを openModal の引数 id に渡す。
-
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 のドキュメントへのリンクを載せておきます。
翻訳がダメなところがありますので、意味不明の場合は英語版を読むことをお勧めします。