狛ログ

2022年4月22日金曜日

【C#】モーダル内に非同期でデータを表示させたかった話【ajax】

おはようございます。こんにちは。こんばんは。
オフィス狛 開発担当のnagoです。

今回は、最近初めて使用したC#(ASP.NET Core MVC)で、
なるほどなぁと思ったモーダル実装についてまとめたいと思います。
DBはPostgreSQL、ビューにはBootstrapを使用しました。

<やりたかったこと>
親画面からボタンクリック

親画面とは別のcshtmlファイルに記述したモーダルを開きつつ、
DBから持ってきたデータをモーダル内に表示させる

<できたもの>
コンサートなどのチケット購入サイト風サンプルを作成してみました。

・Topページ


↓詳細ボタンを押下したら

こんなかんじで表示。
・モーダル

データはこんなかんじ。

処理全体の流れとしては、
①親画面にモーダルのcshtmlを入れ込むためのdivを置いておく
②ボタンが押されたら、ajaxで非同期でコントローラにデータ取得用パラメータを渡す
③コントローラでデータ取得
④取得したデータを成形してビューモデルにセット
⑤コントローラからPertialViewでjsにcshtmlとビューモデルを返却する
⑥モーダル表示
というかんじで作ってみました。

<ソース>
・TopController.cs
using Microsoft.AspNetCore.Mvc;
using DotNetTest.Models;

namespace DotNetTest.Controllers
{
    public class TopController : Controller
    {
        private readonly DotNetTestContext _context;

        public TopController(DotNetTestContext context){
            this._context = context;
        }

        [HttpGet]
        public IActionResult Top() {
            return View(this._context.performance);
        }
    }
}
・ModalController.cs
using Microsoft.AspNetCore.Mvc;
using DotNetTest.Models;
using System.Linq;

namespace DotNetTest.Controllers
{
    public class ModalController : Controller
    {
        private readonly DotNetTestContext _context;

        public ModalController(DotNetTestContext context){
            this._context = context;
        }

        [HttpPost]
        public IActionResult ShowModal(ModalViewModel viewModel) {
            try
            {
                if ( viewModel.id != string.Empty && _context.performance != null) {
                    if( Int32.TryParse( viewModel.id, out int intId )) {
                        // レコード取得
                        var performance = _context.performance.SingleOrDefault(p => p.id == intId);
                        if (performance != null) {
                            viewModel.name = performance.name;
                            viewModel.date = performance.date.ToString("yyyy年MM月dd日 HH:mm");
                            viewModel.place = performance.place;
                            viewModel.description = performance.description;
                            viewModel.errorMessage = string.Empty;
                            // ビューとビューモデルを返却
                            return PartialView("Modal", viewModel);
                        }
                    }
                }
                // エラーの場合はエラーメッセージ入りビューモデルを返却
                return Json(viewModel);
            }
            catch (System.Exception)
            {
                throw;
            }
        }
    }
}
・DotNetTestContext.cs
using Microsoft.EntityFrameworkCore;
using DotNetTest.Models;

namespace DotNetTest.Models
{
    public class DotNetTestContext: DbContext
    {
        public DotNetTestContext(DbContextOptions options) : base(options)
        {           
        }
        public DbSet performance { get; set; } = null!;
    }
}
・Performance.cs
namespace DotNetTest.Models
{
    public class performance
    {
        public int id { get; set; }
        public string name { get; set; } = string.Empty;
        public DateTime date { get; set; }
        public string place { get; set; } = string.Empty;
        public string description { get; set; } = string.Empty;
    }
}
・ModalViewModel.cs
using Microsoft.AspNetCore.Mvc;

namespace DotNetTest.Models
{
    [BindProperties]
    public class ModalViewModel
    {
        public string id { get; set; } = string.Empty;
        public string name { get; set; } = string.Empty;
        public string date { get; set; } = string.Empty;
        public string place { get; set; } = string.Empty;
        public string description { get; set; } = string.Empty;
        public string errorMessage { get; set; } = "エラーが発生しました";
    }
}
・Top.cshtml
@model IEnumerable<DotNetTest.Models.performance>
@{
  ViewData["Title"] = "Top";
}
<!doctype html>
<html lang="ja" >
<head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
  <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" >
</head>
<body>
  <table>
    <tr>
      <td colspan="2">
        <p class="error_message" id="error_message"></p>
      </td>
    </tr>
    @foreach(var item in Model)
    {
      <tr>
        <td>
          <p>・ @item.name @item.date.ToString("yyyy年M月d日 HH:mm")〜</p>
        </td>
        <td class="button_td">
          <button type="button" class="btn btn-primary" data-toggle="modal" value="@item.id" onclick="openModal(this.value);">
            詳細
          </button>
        </td>
      </tr>
    }
  </table>
  <div id="modal_base"></div>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  <script src="~/js/site.js" asp-append-version="true"></script>
</body>
</html>
・Modal.cshtml
@using DotNetTest.Models
@model ModalViewModel
<!-- Modal -->
<div class="modal fade" id="modal" tabindex="-1" aria-labelledby="modalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">@Model.name</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <p>
          公演日時:@Model.date 〜<br>
          会場:@Model.place<br>
          説明:@Model.description
        </p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-primary">
          予約
        </button>
      </div>
    </div>
  </div>
</div>
・site.js
function openModal(id) {
    $('#error_message').val('');
    // コントローラ名/メソッド名
    const url = new URL(window.location.href) + 'Modal/ShowModal';
    // パラメータ成形
    let form = new FormData();
    form.append('id', id);
    $.ajax({
        url: url,
        type: 'POST',
        processData: false,
        contentType: false,
        data: form
    }).done(function(data){
        const data_stringify = JSON.stringify(data);
        const data_json = JSON.parse(data_stringify);
        if(data_json.errorMessage != null) {
            $('#error_message').append(data_json.errorMessage);
        } else {
            // 親画面に置いておいたdivに、コントローラから受け取ったモーダルのcshtmlをセットする
            $('#modal_base').html(data);
            // モーダル表示
            $('#modal_base').find('.modal').modal('show');
        }
    });
}

親画面と子画面(モーダル)のcshtmlを別ファイルで管理したかったのと、
jsファイルも別ファイルで管理したかったということがあり、苦戦したのですが、
これで思い通りの動きに出来た時はすごく嬉しかったです。
もし同じようなことで躓いた方がいらしたら、参考になれば幸いです。

参考URL:
https://docs.microsoft.com/ja-jp/dotnet/api/system.web.mvc.controller.partialview?view=aspnet-mvc-5.2
, , ,

0 件のコメント:

コメントを投稿