【ASP.NET MVC5】実務で使える簡単なCRUD機能の作成
概要
プログラミング初級の方が、実践の場でプログラミングをする際に、
必ずと言って作るであろうCRUD(クラッド)機能。
- Create(生成)
- Read(読取)
- Update(更新)
- Delete(削除)
これら4つの機能をまとめてCRUD(クラッド)と呼ばれています。
この記事では、ASP.NET MVC5のプロジェクトにて、CRUD機能の
を作成する方法をまとめています。
今回は、商品データをデータベース上で管理して表示、追加、編集できる簡単なシステムを開発していきます。
作成する画面は、以下の通りです。
No | 画面名 | CRUD | 画面詳細 |
---|---|---|---|
1 | 商品一覧画面 | Read | 商品データを読込、一覧として表示する画面 |
2 | 商品登録画面 | Create | 商品データを新規に登録する画面 |
3 | 商品編集画面 | Update/Delete | 商品データの内容を更新または削除する画面 |
事前準備
使用ツール
・Visual Studio Community 2019
こちらでダウンロード可能です。(無料)
・SQL Server 2019 Developer エディション
こちらでダウンロード可能です。(無料)
使用プロジェクト
こちらの記事で作成したログイン機能のプロジェクトを使用して解説していきます。
- Entity FrameWorkのバージョン:6.0.2
- bootstrapのバージョン:3.4.1
データベース構築
今回は、Microsoft SQL Server でデータベースを作成して、商品テーブル(T_COM)を作成します。
SQL Serverのインストールに関しては、ここでは割愛します。
商品テーブルのカラム情報
No. | 論理名 | 物理名 | データ型 | Not Null | デフォルト | 備考 |
---|---|---|---|---|---|---|
1 | 商品ID | com_id | bigint identity | Yes (PK) | ||
2 | 商品名 | com_name | nvarchar(100) | |||
3 | 商品詳細 | com_detail | nvarchar(max) | |||
4 | 削除フラグ | del_flag | tinyint | Yes | 未削除:0 削除済:1 | |
5 | 登録日時 | create_date | datetime | Yes | ||
6 | 登録ユーザー | create_user | nvarchar(50) | |||
7 | 更新日時 | update_date | datetime | Yes | ||
8 | 更新ユーザー | update_user | nvarchar(50) |
SQL Server上で、以下のSQLを実行してください。
商品テーブル(T_COM)
CREATE TABLE [dbo].[T_COM](
[com_id] [bigint] IDENTITY(1,1) NOT NULL,
[com_name] [nvarchar](100) NULL,
[com_detail] [nvarchar](max) NULL,
[del_flag] [tinyint] NOT NULL,
[create_date] [datetime] NOT NULL,
[create_user] [nvarchar](50) NULL,
[update_date] [datetime] NOT NULL,
[update_user] [nvarchar](50) NULL,
CONSTRAINT [PK_COM] PRIMARY KEY CLUSTERED
(
[com_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
商品テーブル(T_COM)の初期データ
INSERT INTO [dbo].[T_COM] ([com_name], [com_detail], [del_flag], [create_date], [create_user], [update_date], [update_user]) VALUES ('テスト商品1', 'テスト商品1の詳細です。', 0, GETDATE(), 'admin', GETDATE(), 'admin')
INSERT INTO [dbo].[T_COM] ([com_name], [com_detail], [del_flag], [create_date], [create_user], [update_date], [update_user]) VALUES ('テスト商品2', 'テスト商品2の詳細です。', 0, GETDATE(), 'admin', GETDATE(), 'admin')
INSERT INTO [dbo].[T_COM] ([com_name], [com_detail], [del_flag], [create_date], [create_user], [update_date], [update_user]) VALUES ('テスト商品3', 'テスト商品3の詳細です。', 0, GETDATE(), 'admin', GETDATE(), 'admin')
INSERT INTO [dbo].[T_COM] ([com_name], [com_detail], [del_flag], [create_date], [create_user], [update_date], [update_user]) VALUES ('テスト商品4', 'テスト商品4の詳細です。', 0, GETDATE(), 'admin', GETDATE(), 'admin')
INSERT INTO [dbo].[T_COM] ([com_name], [com_detail], [del_flag], [create_date], [create_user], [update_date], [update_user]) VALUES ('テスト商品5', 'テスト商品5の詳細です。', 0, GETDATE(), 'admin', GETDATE(), 'admin')
SQL Server Management StudioでSQLを実行
Entity Data Model更新
データベースでテーブルの追加、初期データ追加ができたら、プロジェクト上でADO.NET Enitiy Data Modelも更新して、
データベースの情報をマッピングする為のEDMファイルを作成します。
(Visual Studio上でEDMファイルを作成する方法は、こちらの記事を参照ください。)
対象のEDMファイルのデザイナー画面を開き、右クリック→「データベースからモデルを更新」を選択します。
EDMのデザイナー画面に、商品テーブル(T_COM)が追加された事を確認できたら、上書き保存します。
一覧表示機能作成
EDMに商品テーブル(T_COM)を追加したので、商品一覧を表示する一覧画面を作っていきましょう。
Modelの作成
まずは一覧画面のデータを表示する際にマッピングするためのモデルを作成します。
プロジェクト内の「Model」ディレクトリにComModel.csを新規作成してください。
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace Mvc5LoginSample1.Models
{
public class ComModel
{
[DisplayName("商品ID")]
public long Com_Id { get; set; }
[DisplayName("商品名")]
[Required(ErrorMessage = "商品名は必須入力です。")]
public string ComName { get; set; }
[DisplayName("詳細")]
public string ComDetail { get; set; }
}
}
一覧には、商品名は必須入力にする為、12行目で
アノテーションの設定(必須、エラーメッセージ)をしておきます。
Controllerの作成
次に商品テーブル(商品)からデータを取得する処理を記述する為、
プロジェクト内の「Controller」ディレクトリにComController.csを新規作成してください。
using Mvc5LoginSample1.DAL;
using Mvc5LoginSample1.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
namespace Mvc5LoginSample1.Controllers
{
public class ComController : Controller
{
/// <summary>
/// 商品一覧
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
var comModelList = new List<ComModel>();
using (var context = new SAMPLEDB01Entities())
{
// DBから商品一覧取得
comModelList = context.T_COM.AsNoTracking()
.Where(x => x.del_flag == 0)
.Select(x =>
new ComModel
{
Com_Id = x.com_id,
ComName = x.com_name,
ComDetail = x.com_detail,
}).ToList();
}
return View(comModelList);
}
}
}
ポイントは商品テーブル(T_COM)から削除フラグが0(未削除)のデータを、作成したComModelクラス型のインスタンスに代入します。
取得した結果をViewに送ります。
※SAMPLEDB01Entitiesは、私がEDMファイルを作成した際に設定したエンティティの名前の為、ご自身で設定したエンティティ名に変更してください。
Viewの作成・修正
続いては一覧画面のViewを作成する為、
プロジェクト内の「View」ディレクトリにComというディレクトリを新規作成し、
その中にindex.cshtmlを新規に作成してください。
@model IEnumerable<Mvc5LoginSample1.Models.ComModel>
<h2>商品一覧</h2>
@Html.ActionLink("新規登録", "Create", null, new {@class = "btn btn-warning" })
<hr>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">
@Html.DisplayNameFor(model => model.Com_Id)
</th>
<th scope="col">
@Html.DisplayNameFor(model => model.ComName)
</th>
<th scope="col">
@Html.DisplayNameFor(model => model.ComDetail)
</th>
<th scope="col">
修正
</th>
</tr>
</thead>
<tbody>
@foreach (var m in Model)
{
<tr name="row">
@*商品ID*@
<td>
@Html.DisplayFor(modelItem => m.Com_Id)
</td>
@*商品名*@
<td>
@Html.DisplayFor(modelItem => m.ComName)
</td>
@*商品詳細*@
<td>
@Html.DisplayFor(modelItem => m.ComDetail)
</td>
<td>
@Html.ActionLink("編集", "Edit", new { id = m.Com_Id }, new { @class = "btn btn-primary" })
</td>
</tr>
}
</tbody>
</table>
次に商品一覧画面に遷移する為のリンクを設定する為、ヘッダーメニューの_Layout.cshtmlに
リンクを追加します。
@*↑省略*@
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@Html.ActionLink("商品管理システム", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("商品一覧", "Index", "Com")</li>
</ul>
@if (Request.IsAuthenticated)
{
<ul class="nav navbar-nav navbar-right">
<li>@Html.ActionLink("ログアウト", null, null, null, new { id = "LogoutLink" })</li>
</ul>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li>@Html.ActionLink("ログイン", "Login", "Auth")</li>
</ul>
}
</div>
</div>
</div>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© @DateTime.Now.Year - 商品管理システム</p>
</footer>
</div>
@*↓省略*@
10、35行目は、商品管理システムなど適当なシステム名を設定しておきましょう。
14行目で商品一覧画面に遷移するリンクを追加します。
ASP.NET MVCプロジェクトのデフォルトで入っているホーム、詳細、問い合わせのリンクは、使用しない為、削除しました。
新規登録機能の作成
次に商品データを新規登録(Create)する機能を作っていきます。
Controllerの修正
一覧機能(Read)で作成したComController.csに新規登録に必要なコードを追加します。
/* ↑ 省略 */
/// <summary>
/// 新規登録 表示
/// </summary>
/// <returns></returns>
[HttpGet]
public ActionResult Create()
{
var model = new ComModel();
return View(model);
}
/// <summary>
/// 新規登録
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ComModel model)
{
try
{
// 入力チェック
if (!this.ModelState.IsValid)
{
return View(model);
}
// 登録処理
using (var context = new SAMPLEDB01Entities())
{
DateTime now = DateTime.Now;
string userID = User.Identity.Name;
T_COM targetData = new T_COM();
targetData.com_name = model.ComName;
targetData.com_detail = model.ComDetail;
targetData.create_date = now;
targetData.create_user = userID;
targetData.update_date = now;
targetData.update_user = userID;
context.T_COM.Add(targetData);
context.SaveChanges();
}
// 一覧画面にリダイレクト
return RedirectToAction("Index");
}
catch (Exception)
{
ModelState.AddModelError("", "問題が発生しました。");
}
return View(model);
}
同じCreateメソッドになっていますが、
画面表示時は、GETの方のメソッドを使用して、登録時はPOSTの方のメソッドを使用しています。
POST時の処理の流れとしては、
入力チェック ⇒ 登録処理 ⇒ 一覧画面にリダイレクトになっています。
※登録処理で商品IDの記載がないですが、商品IDはテーブルの設定で自動採番するようにしている為、登録時に記載する必要がありません。
Viewの作成
一覧画面(Read)で作成したComディレクトリ上に、Create.cshtmlを新規に作成してください。
@model Mvc5LoginSample1.Models.ComModel
<h2>商品登録</h2>
@using (@Html.BeginForm("Create", "Com", FormMethod.Post, new { @id = "comCreateForm" }))
{
@Html.AntiForgeryToken()
<div class="text-danger">
@Html.ValidationSummary(false)
</div>
<hr />
<div class="form-horizontal">
<div class="form-group">
<label for="name" class="col-xs-1 control-label">@Html.DisplayNameFor(m => m.ComName)</label>
<div class="col-xs-11">
@Html.TextBoxFor(m => m.ComName, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<label for="name" class="col-xs-1 control-label">@Html.DisplayNameFor(m => m.ComDetail)</label>
<div class="col-xs-11">
@Html.TextAreaFor(m => m.ComDetail, 5, 500, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-xs-offset-1 col-xs-11">
<input type="submit" value="登録" class="btn btn-primary" /> |
@Html.ActionLink("戻る", "Index", null, new { @class = "btn btn-primary" })
</div>
</div>
</div>
}
編集機能(更新・削除)の作成
新規作成画面が完成したら、最後に更新、削除機能を作成します。
新規作成画面でContollerとViewを作成した際に、
Viewのファイル名とContorollerのメソッド名は、一致している事に気が付きましたか?
その流れで更新・削除処理を実装すると、Viewを更新用、削除用の2つ作成しないといけません。
実務の場合、更新、削除の画面が、別々に分かれているCRUDアプリは、ほぼないでしょう!
その為、今回はViewは1つのファイルにして更新、削除ができるようにします。
1つのViewファイルから処理するメソッドをSubmitした時に分ける方法がないか?
探していたところ、こちらのサイトに方法が書かれていました。
Contorollerのメソッドに、ActionNameとButtonのカスタムのアノテーションを設定することで可能になります。
しかし、カスタムアノテーションの為のクラスを別途作成する必要があります。
別の画面でも共通で使用できるようにする為、共通のBaseContollerのファイルを作成して、
ComContollerの継承元をBaseContollerに修正します。
Contollerの修正
BaseContollerの作成
まずは、プロジェクト内の「Controller」ディレクトリにBaseController.csを新規作成してください。
using System.Reflection;
using System.Web.Mvc;
namespace Mvc5LoginSample1.Controllers
{
public class BaseController : Controller
{
protected class ButtonAttribute : ActionMethodSelectorAttribute
{
public string ButtonName { get; set; }
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
return controllerContext.Controller.ValueProvider.GetValue(ButtonName) != null;
}
}
}
}
Contollerの修正
次にComControllerの継承元クラスをControllerからBaseControllerに修正。
編集画面表示、更新用、削除用のメソッドを追加します。
using Mvc5LoginSample1.DAL;
using Mvc5LoginSample1.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
namespace Mvc5LoginSample1.Controllers
{
public class ComController : BaseController
{
/* 新規作成関連の記載省略しています。 */
/// <summary>
/// 編集 表示
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet]
public ActionResult Edit(int? id)
{
var model = new ComModel();
if (id == null)
{
return RedirectToAction("Create");
}
T_COM comDetail = null;
using (var context = new SAMPLEDB01Entities())
{
comDetail = context.T_COM.AsNoTracking().Where(x => x.com_id == id).FirstOrDefault();
}
if (comDetail == null)
{
return HttpNotFound();
}
model.Com_Id = comDetail.com_id;
model.ComName = comDetail.com_name;
model.ComDetail = comDetail.com_detail;
return View(model);
}
/// <summary>
/// 編集
/// </summary>
/// <param name="comModel"></param>
/// <returns></returns>
[HttpPost]
[ActionName("Edit")]
[Button(ButtonName = "Save")]
[ValidateAntiForgeryToken]
public ActionResult Save(ComModel model)
{
try
{
// 入力チェック
if (!this.ModelState.IsValid)
{
return View(model);
}
// 更新処理
T_COM com = null;
using (var context = new SAMPLEDB01Entities())
{
var now = DateTime.Now;
string userID = User.Identity.Name;
com = context.T_COM.Where(x => x.com_id == model.Com_Id).FirstOrDefault();
com.com_name = model.ComName;
com.com_detail = model.ComDetail;
com.update_date = now;
com.update_user = userID;
context.SaveChanges();
}
// 一覧画面にリダイレクト
return RedirectToAction("Index");
}
catch (Exception)
{
ModelState.AddModelError("", "問題が発生しました。");
}
return View(model);
}
/// <summary>
/// 削除
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
[ActionName("Edit")]
[Button(ButtonName = "Delete")]
[ValidateAntiForgeryToken]
public ActionResult Delete(ComModel model)
{
try
{
// 削除処理
T_COM com = null;
using (var context = new SAMPLEDB01Entities())
{
var now = DateTime.Now;
string userID = User.Identity.Name;
com = context.T_COM.Where(x => x.com_id == model.Com_Id).FirstOrDefault();
com.del_flag = 1;
com.update_date = now;
com.update_user = userID;
context.SaveChanges();
}
// 一覧画面にリダイレクト
return RedirectToAction("Index");
}
catch (Exception)
{
ModelState.AddModelError("", "問題が発生しました。");
}
return View(model);
}
}
}
メソッド名の上に、
[ActionName("Edit")]
[Button(ButtonName = "Save")]
を設定することで、Submit時の処理を分岐しています。
削除処理に関しては、データを直接削除するわけでなく、
商品テーブル(T_COM)の削除フラグを1(削除済)に更新して、
1(削除済)の商品データに関しては、一覧画面で読込する際に、
取得しないようにする事で、削除されたように見せています。
※こういったフラグの値を変更して削除する方法を論理削除と呼ばれています。
Viewの作成
Create.cshtmlと同じようにComディレクトリ上に、Edit.cshtmlを新規に作成してください。
内容は、Create.cshtmlとほぼ同じですが、
タイトルと配置しているボタンが異なるだけで、他は変わりません。
@model Mvc5LoginSample1.Models.ComModel
<h2>商品編集</h2>
@using (@Html.BeginForm("Edit", "Com", FormMethod.Post, new { id = "comEditForm" }))
{
@Html.AntiForgeryToken()
<div class="text-danger">
@Html.ValidationSummary(false)
</div>
<hr />
<div class="form-horizontal">
<div class="form-group">
<label for="name" class="col-xs-1 control-label">@Html.DisplayNameFor(m => m.ComName)</label>
<div class="col-xs-11">
@Html.TextBoxFor(m => m.ComName, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<label for="name" class="col-xs-1 control-label">@Html.DisplayNameFor(m => m.ComDetail)</label>
<div class="col-xs-11">
@Html.TextAreaFor(m => m.ComDetail, 5, 500, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-xs-offset-1 col-xs-11">
<input type="submit" value="削除" name="Delete" id="comDelete" class="btn btn-danger" /> |
<input type="submit" value="更新" name="Save" class="btn btn-primary" /> |
@Html.ActionLink("戻る", "Index", null, new { @class = "btn btn-primary" })
@Html.HiddenFor(m => m.Com_Id)
</div>
</div>
</div>
}
※27、28行目の削除、更新ボタンのname属性は、ControllerのButtonNameで記載した文字列と同じにしてください。
JSファイルの修正
これで削除処理が実行されるようになりましたが、
このままでは削除ボタンをクリック後、問答無用に削除処理が実行されてしまい、
誤操作などした場合に、取り返しがつかなくなります。
その為、確認ダイアログを表示して、「OK」をクリックした場合のみ削除処理が実行されるようにします。
※今回はcommon.jsというjsファイルに記載します。
新規にcommon.jsを作成・読込の方法は、こちらの記事で書いてあります。
$(function () {
$("#comDelete").click(function () {
var ret = confirm("削除してもよろしいですか?");
if (!ret) {
return false;
}
});
})
まとめ
ここまで実装できたら、早速実行してみましょう!
【実行結果】
初期表示
商品名が未入力の場合、バリデーションエラー発生!
商品名を入力して登録!
登録した商品が表示されている事を確認
編集画面で、内容を更新!
更新された内容が、反映されている事を確認
商品5のデータを削除
商品5のデータが削除されている事を確認
今回はかなりボリュームのある内容でしたが、如何でしたか?
現場に入って多くの初心者が、まず初めに任されるであろう機能が、
こういったCRUD機能の実装です。
これができるか?できないか?で
次のステップに進める登竜門のような機能です。
自分で一度作成した事があれば、
言語が変わろうが、やることは一緒です!
是非、練習と思って取り組んでみてください(^^)
それでは、今回もありがとうございました!
参考
ASP.NET MVC: Using ActionMethodSelectorAttribute to Handle Multiple Submit Buttons