記事詳細


投稿日
2026年09月10日09時22分
タイトル

ASP.NET Core MVC : <select asp-for="IncomeCategoryId" class="form-control" id="IncomeCategoryId" asp-items="@(ViewData["IncomeCategoryId"] as SelectList)"> <option value="add">+ 項目を追加</option> のようなCreateView内にある<select>の”+ 項目の追加”をクリック、新項目を追加して、DB に保

内容

以下は、前回のサンプルを改良した ASP.NET Core MVC 実装例です。
主な変更点は以下の通りです。

  • CSRF 対策 を @Html.AntiForgeryToken() と RequestVerificationToken ヘッダーで確実に実装
  • 新規カテゴリ追加時の UI 改善(追加後に即選択状態にする)
  • エラーハンドリング強化(サーバー側・クライアント側両方)
  • SelectList の更新位置を「+ 項目を追加」の直前」に固定

1. ViewModel

Csharp
public class IncomeViewModel { public int IncomeCategoryId { get; set; } public IEnumerable<SelectListItem> IncomeCategories { get; set; } }

2. Controller

Csharp
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using System.Linq; public class IncomeController : Controller { private readonly AppDbContext _context; public IncomeController(AppDbContext context) { _context = context; } // GET: Create public IActionResult Create() { var vm = new IncomeViewModel { IncomeCategories = _context.IncomeCategories .Select(c => new SelectListItem { Value = c.Id.ToString(), Text = c.Name }) .ToList() }; return View(vm); } // POST: Create [HttpPost] [ValidateAntiForgeryToken] public IActionResult Create(IncomeViewModel model) { if (model.IncomeCategoryId <= 0) { ModelState.AddModelError("", "カテゴリを選択してください"); } if (!ModelState.IsValid) { model.IncomeCategories = _context.IncomeCategories .Select(c => new SelectListItem { Value = c.Id.ToString(), Text = c.Name }) .ToList(); return View(model); } // 保存処理例 // var income = new Income { CategoryId = model.IncomeCategoryId, ... }; // _context.Incomes.Add(income); // _context.SaveChanges(); return RedirectToAction("Index"); } // Ajax: 新しいカテゴリ追加 [HttpPost] [ValidateAntiForgeryToken] public IActionResult AddCategory([FromBody] CategoryRequest request) { if (string.IsNullOrWhiteSpace(request?.Name)) return BadRequest(new { message = "カテゴリ名は必須です" }); var category = new IncomeCategory { Name = request.Name.Trim() }; _context.IncomeCategories.Add(category); _context.SaveChanges(); return Json(new { id = category.Id, name = category.Name }); } public class CategoryRequest { public string Name { get; set; } } }

3. Create.cshtml

Razor
@model IncomeViewModel <form asp-action="Create" method="post"> @Html.AntiForgeryToken() <div class="form-group"> <label asp-for="IncomeCategoryId"></label> <select asp-for="IncomeCategoryId" class="form-control" id="IncomeCategoryId" asp-items="Model.IncomeCategories"> <option value="">-- 選択してください --</option> <option value="add">+ 項目を追加</option> </select> </div> <button type="submit" class="btn btn-primary">保存</button> </form> <!-- モーダル --> <div class="modal fade" id="addCategoryModal" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">新しいカテゴリを追加</h5> <button type="button" class="btn-close" data-bs-dismiss="modal"></button> </div> <div class="modal-body"> <input type="text" id="newCategoryName" class="form-control" placeholder="カテゴリ名"> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">キャンセル</button> <button type="button" id="saveCategoryBtn" class="btn btn-primary">追加</button> </div> </div> </div> </div> @section Scripts { <script> const categorySelect = document.getElementById("IncomeCategoryId"); const modalElement = document.getElementById('addCategoryModal'); const modalInstance = new bootstrap.Modal(modalElement); categorySelect.addEventListener("change", function () { if (this.value === "add") { this.value = ""; modalInstance.show(); } }); document.getElementById("saveCategoryBtn").addEventListener("click", function () { const name = document.getElementById("newCategoryName").value.trim(); if (!name) { alert("カテゴリ名を入力してください"); return; } fetch('@Url.Action("AddCategory", "Income")', { method: 'POST', headers: { 'Content-Type': 'application/json', 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value }, body: JSON.stringify({ name: name }) }) .then(res => { if (!res.ok) return res.json().then(err => { throw new Error(err.message || "追加に失敗しました"); }); return res.json(); }) .then(data => { // 「+ 項目を追加」の直前に挿入 const addOptionIndex = categorySelect.options.length - 1; const option = new Option(data.name, data.id, true, true); categorySelect.add(option, addOptionIndex); modalInstance.hide(); document.getElementById("newCategoryName").value = ""; }) .catch(err => alert(err.message)); }); </script> }

改良ポイント

  1. CSRF 対策

    • Razor 側で @Html.AntiForgeryToken() を追加
    • Fetch API で RequestVerificationToken ヘッダーを送信
  2. UI 改善

    • 新規追加後に即選択状態にする
    • 「+ 項目を追加」の直前に新しい <option> を挿入
  3. エラーハンドリング強化

    • サーバー側で BadRequest を返す場合に JSON メッセージを返却
    • クライアント側で .catch() でエラー表示

この形にすると、ページ遷移なしでカテゴリ追加ができ、
セキュリティとユーザビリティの両方を確保できます。

もし希望があれば、このコードを 完全に非同期で保存まで行うバージョン に拡張して、
カテゴリ追加後にそのまま新規レコードも保存できるようにできます。
それも作りますか?

Powered by Froala Editor

添付画像

画像はありません
Genreのカテゴリ-名
Views
編集 | 記事一覧