記事詳細


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

以下は、前回のサンプルを改良した ASP.NET Core MVC + SweetAlert2 + AJAX 実装例です。 主な変更点は以下です。 コード構造の整理([ValidateAntiForgeryToken] の追加、エラーハンドリング強化) SelectList 更新処理の改善(既存項目重複チェック) SweetAlert2 の UX 改善(入力バリデーション、ローディング表示) 子セレクトの動的ロード API を追加(親変更時に自動更新)

内容

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

  • コード構造の整理[ValidateAntiForgeryToken] の追加、エラーハンドリング強化)
  • SelectList 更新処理の改善(既存項目重複チェック)
  • SweetAlert2 の UX 改善(入力バリデーション、ローディング表示)
  • 子セレクトの動的ロード API を追加(親変更時に自動更新)

1. モデル

Csharp
// Models/PaymentType.cs public class PaymentType { public int Id { get; set; } public string Name { get; set; } = string.Empty; public ICollection<PaymentMethod> PaymentMethods { get; set; } = new List<PaymentMethod>(); } // Models/PaymentMethod.cs public class PaymentMethod { public int Id { get; set; } public string Name { get; set; } = string.Empty; public int PaymentTypeId { get; set; } public PaymentType? PaymentType { get; set; } }

2. コントローラ

Csharp
// Controllers/PaymentsController.cs using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.EntityFrameworkCore; public class PaymentsController : Controller { private readonly AppDbContext _context; public PaymentsController(AppDbContext context) { _context = context; } public IActionResult Create() { ViewData["PaymentTypeId"] = new SelectList(_context.PaymentTypes.AsNoTracking(), "Id", "Name"); ViewData["PaymentMethodId"] = new SelectList(Enumerable.Empty<SelectListItem>()); return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create(Payment payment) { if (ModelState.IsValid) { _context.Add(payment); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } return View(payment); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> AddPaymentType([FromBody] PaymentType model) { if (string.IsNullOrWhiteSpace(model.Name)) return BadRequest("Name is required"); if (await _context.PaymentTypes.AnyAsync(x => x.Name == model.Name)) return Conflict("既に存在します"); _context.PaymentTypes.Add(model); await _context.SaveChangesAsync(); return Json(new { id = model.Id, name = model.Name }); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> AddPaymentMethod([FromBody] PaymentMethod model) { if (string.IsNullOrWhiteSpace(model.Name) || model.PaymentTypeId <= 0) return BadRequest("Invalid data"); if (await _context.PaymentMethods.AnyAsync(x => x.Name == model.Name && x.PaymentTypeId == model.PaymentTypeId)) return Conflict("既に存在します"); _context.PaymentMethods.Add(model); await _context.SaveChangesAsync(); return Json(new { id = model.Id, name = model.Name }); } [HttpGet] public async Task<IActionResult> GetPaymentMethodsByTypeId(int typeId) { var methods = await _context.PaymentMethods .Where(m => m.PaymentTypeId == typeId) .Select(m => new { id = m.Id, name = m.Name }) .ToListAsync(); return Json(methods); } }

3. Create.cshtml

Razor
@model Payment <div class="form-group"> <label asp-for="PaymentTypeId"></label> <select asp-for="PaymentTypeId" id="PaymentTypeId" class="form-control" asp-items="@(ViewData["PaymentTypeId"] as SelectList)"> <option value="">-- 選択 --</option> </select> <button type="button" id="btnAddPaymentType" class="btn btn-sm btn-primary mt-1">+ 新規追加</button> </div> <div class="form-group"> <label asp-for="PaymentMethodId"></label> <select asp-for="PaymentMethodId" id="PaymentMethodId" class="form-control" asp-items="@(ViewData["PaymentMethodId"] as SelectList)"> <option value="">-- 選択 --</option> </select> <button type="button" id="btnAddPaymentMethod" class="btn btn-sm btn-primary mt-1">+ 新規追加</button> </div> @section Scripts { <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script> const token = '@Antiforgery.GetTokens(HttpContext).RequestToken'; // 親変更時に子をロード document.getElementById("PaymentTypeId").addEventListener("change", function () { let typeId = this.value; let methodSelect = document.getElementById("PaymentMethodId"); methodSelect.innerHTML = '<option value="">-- 選択 --</option>'; if (!typeId) return; fetch(`@Url.Action("GetPaymentMethodsByTypeId")?typeId=${typeId}`) .then(res => res.json()) .then(data => { data.forEach(m => { methodSelect.add(new Option(m.name, m.id)); }); }); }); // PaymentType 追加 document.getElementById("btnAddPaymentType").addEventListener("click", function () { Swal.fire({ title: '新しい支払タイプ', input: 'text', inputPlaceholder: 'タイプ名を入力', inputValidator: value => !value && '必須項目です', showCancelButton: true, confirmButtonText: '追加' }).then((result) => { if (result.isConfirmed) { Swal.showLoading(); fetch('@Url.Action("AddPaymentType")', { method: 'POST', headers: { 'Content-Type': 'application/json', 'RequestVerificationToken': token }, body: JSON.stringify({ name: result.value }) }) .then(res => { if (res.status === 409) throw new Error("既に存在します"); return res.json(); }) .then(data => { let select = document.getElementById("PaymentTypeId"); if (![...select.options].some(o => o.value == data.id)) { select.add(new Option(data.name, data.id, true, true)); } Swal.fire('追加しました', '', 'success'); }) .catch(err => Swal.fire('エラー', err.message, 'error')); } }); }); // PaymentMethod 追加 document.getElementById("btnAddPaymentMethod").addEventListener("click", function () { let paymentTypeId = document.getElementById("PaymentTypeId").value; if (!paymentTypeId) { Swal.fire('先に支払タイプを選択してください', '', 'warning'); return; } Swal.fire({ title: '新しい支払方法', input: 'text',                 inputPlaceholder: '

Powered by Froala Editor

添付画像

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