【ASP.NET(C#)2】SQLServer(MSSQL)でCRUDを作成して画面表示

今回は前々回記事の続きです。ひとまずDocker環境へのデプロイが終わったので、いつでも切り替えが可能になりました。よってこれからはローカル環境での実施がメインの記事になると思います。前々回の記事をご覧になっていない方は下記リンクから参照できます。

まず操作するDB専用のソースを作成

対象のDBに対して登録等を行うのでメイン画面であるHomeController.csとは別にDBを操作する専用のコントローラーを作りましょう。本記事ではControllersの下にTestsController.csというファイルを作成します。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Aspnet_test2.Models;
using System.Threading.Tasks;

namespace Aspnet_test2.Controllers
{
    public class TestsController : Controller
    {
        private readonly ApplicationDbContext _context;

        public TestsController(ApplicationDbContext context)
        {
            _context = context;
        }

        // GET: Tests
        public async Task<IActionResult> Index()
        {
            return View(await _context.TestDb.ToListAsync());
        }
    }
}

ソース構成自体は前々回の記事で作成したHomeController.csにDBの中身を渡す作りと変わらないです。Indexが定義できましたので次は画面側を作ります。Viewsフォルダの下にTestsフォルダを作成し、その下にIndex.cshtmlを作成します。

Index.cshtml

@model IEnumerable<Aspnet_test2.Models.TestDb>

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

<h1>Index</h1>

<p>
    <a asp-action="Create">新規作成</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Id)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Test)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Update_Time)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Id)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Test)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Update_Time)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.Id">編集</a> |
                <a asp-action="Details" asp-route-id="@item.Id">詳細</a> |
                <a asp-action="Delete" asp-route-id="@item.Id">削除</a>
            </td>
        </tr>
}
    </tbody>
</table>

Readの画面

これでCRUDのうち、Readが完成しました。実行後にURLの所にTestsを付けてアクセスすると下記のような画面が表示されました。

トップページからアクセスできるようにする

今回作成しようとしているのは管理画面側なので通常はメインページからはリンクさせません。実運用を考えたら右上にログイン認証ボタンを作り、ログインが出来たら自動でTestsページに遷移するというやり方が通常かと思います。

ただ、今回はテストページなのでフレーム部分からTestsページにアクセスするように作り変えたいと思います。

Sharedフォルダの下にある_Layout.cshtmlがフレームに相当する部分ですのでHome/Privacyが書いてある所に下記コードを追加するだけです。

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-controller="Tests" asp-action="Index">Tests</a>
</li>

asp-controller=”Tests” asp-action=”Index” はTestsController.csのIndexを呼びに行くという意味になります。上の画像には既にTestsのリンクを作った後のものを貼っております。

CRUDのC(Create)を作成する

Readが終わったので次は追加画面を作ってみようと思います。まずはURLのCreateが受けられるようにTestsController.csに処理を追加します。

// GET: Tests/Create 作成画面
public IActionResult Create()
{
    return View();
}

// POST: Tests/Create 作成画面でCreateボタンを押した場合
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Test,Update_Time")] TestDb testdb)
{
    testdb.Id = Guid.NewGuid().ToString();
    testdb.Update_Time = DateTime.Now;

    _context.Add(testdb);
    await _context.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
}

次にViews/Testsの下にCreate.cshtmlを作成

Create.cshtml

@model Aspnet_test2.Models.TestDb

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

<h1>Create</h1>

<h4>Book</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Test" class="control-label"></label>
                <input asp-for="Test" class="form-control" />
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">戻る</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

実行結果。クリエートテスト2を文章に入力し、Createボタンを押下

するとIndex画面に自動でリダイレクトし、IdにGuid。Update_Timeに現在時刻が自動で設定される。

画面をAPIに送る時の注意点

public async Task<IActionResult> Create([Bind("Id,Test,Update_Time")] TestDb testdb)

TestDb testdbの部分ですが、これは必ずDB名の小文字固定にしてください。最初TestDb testにしたのですがうまく画面で入力した名前がAPIに引き継がれませんでした。

CRUDのU(Update)を作成する

同じような手順で今度は更新画面を作ります。考え方は挿入と一緒ですが、指定したデータのIDを渡してあらかじめ入力するデータを表示しておく所が違います。 URLのEditが受けられるようにTestsController.csに処理を追加します。

// GET: Tests/Edit/5
public async Task<IActionResult> Edit(string id)
{
    if (id == null)
    {
        return NotFound();
    }

    var testdb = await _context.TestDb.FindAsync(id);
    if (testdb == null)
    {
        return NotFound();
    }
    return View(testdb);
}

// POST: Tests/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("Id,Test,Update_Time")] TestDb testdb)
{
    if (id != testdb.Id)
    {
        return NotFound();
    }

    try
    {
        testdb.Update_Time = DateTime.Now;
        _context.Update(testdb);
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!BookExists(testdb.Id))
        {
            return NotFound();
        }
        else
        {
            ModelState.AddModelError(string.Empty, "競合が検出されました。");
            return View(testdb);
        }
    }
    return RedirectToAction(nameof(Index));
}

private bool BookExists(string id)
{
    return _context.TestDb.Any(e => e.Id == id);
}

次にViews/Testsの下にEdit.cshtmlを作成

Edit.cshtml

@model Aspnet_test2.Models.TestDb

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

<h1>編集</h1>

<h4>TestDb</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Test" class="control-label"></label>
                <input asp-for="Test" class="form-control" />
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">戻る</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

中の構成自体はあまり作成時と変わっておりませんが、呼び元で選択したIdテーブルを渡しているのであらかじめデータが表示されるようになっています。

実行結果→対象のデータの編集を選択

あからじめクリエートテスト44が書かれた状態で表示されるのでこの値を自由に編集し、Save

するとIdは変化していないがTestの変更後と更新時間が現在の時間になって表示される。

CRUDのD(Delete)を作成する

最後は削除です。こちらも編集のように指定したIdを渡して削除画面を表示させます。 URLのDeleteが受けられるようにTestsController.csに処理を追加します。

// GET: Tests/Delete/5
public async Task<IActionResult> Delete(string id)
{
    if (id == null)
    {
        return NotFound();
    }

    var testdb = await _context.TestDb.FirstOrDefaultAsync(m => m.Id == id);
    if (testdb == null)
    {
        return NotFound();
    }

    return View(testdb);
}

// POST: Tests/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(string id)
{
    var testdb = await _context.TestDb.FindAsync(id);
    _context.TestDb.Remove(testdb);
    await _context.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
}

次にViews/Testsの下にDelete.cshtmlを作成

Delete .cshtml

@model Aspnet_test2.Models.TestDb

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

<h1>削除</h1>

<div>
    <h4>Tests</h4>
    <hr />
    <dl class="row">
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.Test)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.Test)
        </dd>
    </dl>
    
    <form asp-action="Delete">
        <input type="hidden" asp-for="Id" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-action="Index">戻る</a>
    </form>
</div>

実行結果→編集と同じように編集したい行で削除を押下

削除画面でデータを確認し、Deleteボタンを押下

Index画面にリダイレクトされた時には指定したクリエートテスト2が消えています。

CRUDまとめ

追加変更したソース
  • Controllers/TestsController.cs (新規)Index(Read)/Create/Delete/Edit(Update)の処理追加
  • Views/Tests/Index.cshtml (新規)設定のメイン画面の追加
  • Views/Tests/Create.cshtml(新規)作成画面の追加
  • Views/Tests/Edit.cshtml(新規)編集画面の追加
  • Views/Tests/Delete.cshtml(新規)削除画面の追加

今回もソースを見て頂けるとわかりますが、EntityFrameworkを使いこなすとLINQをいじるのとほぼ同じ要領でデータの制御ができてしまうので非常に楽ですよね。SQLを一切使わないのって楽^^

現在はちょっとわかりませんが1~3年くらい前にサーバサイドをJavaで作った時はSpringBootを使ったとしてもSQLの記述が必要だったと思います。なのでASP.NETでなくともバックエンドをC#で書くのは全然有りだと思いました。

また本来は今回紹介したソースはスキャフォールディング機能を使えば簡単にできてしまうそうです。下記書籍にあったので使おうと思ってましたがバージョン違いなのかエラーが出てうまく作成できませんでした。次回はまた別のことをやりたいと思いますが機会があれば スキャフォールディング機能 についても触れたいと思います。

参考文献

・速習 ASP.NET Core 3 速習シリーズ Kindle版 著:山田 祥寛

今回もこの書籍を参考にさせて頂きました。ソース内容や処理の細かい解説はこの記事では割愛しますので是非この書籍を見て頂きたいと思います!